diff --git a/apps/SOURCES b/apps/SOURCES index 923e979af5..f81261f766 100644 --- a/apps/SOURCES +++ b/apps/SOURCES @@ -300,8 +300,10 @@ keymaps/keymap-echor1.c keymaps/keymap-surfansf28.c #elif CONFIG_KEYPAD == RG_NANO_PAD keymaps/keymap-rgnano.c -#elif CONFIG_KEYPAD == CTRU_PAD -keymaps/keymap-ctru.c +#elif CONFIG_KEYPAD == N3DS_PAD +keymaps/keymap-3ds.c #elif CONFIG_KEYPAD == HIBY_R3PROII_PAD keymaps/keymap-hibyr3proii.c +#elif CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD +keymaps/keymap-hidizsap80max.c #endif diff --git a/apps/bitmaps/native/SOURCES b/apps/bitmaps/native/SOURCES index f0149175f3..9812851388 100644 --- a/apps/bitmaps/native/SOURCES +++ b/apps/bitmaps/native/SOURCES @@ -46,7 +46,7 @@ toolsicon.130x130x16.bmp hibyicon.70x70x16.bmp rockboxicon.70x70x16.bmp toolsicon.70x70x16.bmp -#elif (defined(XDUOO_X3II) || defined(XDUOO_X20) || defined(EROS_Q) || defined(SURFANS_F28) || defined(HIBY_R3PROII) || defined(HIBY_R1)) +#elif (defined(XDUOO_X3II) || defined(XDUOO_X20) || defined(EROS_Q) || defined(SURFANS_F28) || defined(HIBY_R3PROII) || defined(HIBY_R1) || defined(HIDIZS_AP80MAX)) hibyicon.130x130x16.bmp rockboxicon.130x130x16.bmp toolsicon.130x130x16.bmp diff --git a/apps/bookmark.c b/apps/bookmark.c index 8c4cb18211..a8a0961c8a 100644 --- a/apps/bookmark.c +++ b/apps/bookmark.c @@ -692,7 +692,7 @@ static int buffer_bookmarks(struct bookmark_list* bookmarks, int first_line) return -1; } - if ((first_line != 0) && ((size_t) filesize(file) < bookmarks->buffer_size + if ((first_line != 0) && ((size_t) ffilesize(file) < bookmarks->buffer_size - sizeof(*bookmarks) - (sizeof(char*) * bookmarks->total_count))) { /* Entire file fits in buffer */ diff --git a/apps/buffering.c b/apps/buffering.c index a4c179af41..8b9617e98f 100644 --- a/apps/buffering.c +++ b/apps/buffering.c @@ -982,7 +982,7 @@ int bufopen(const char *file, off_t offset, enum data_type type, #endif /* HAVE_ALBUMART */ if (size == 0) - size = filesize(fd); + size = ffilesize(fd); unsigned int hflags = 0; if (type == TYPE_PACKET_AUDIO || type == TYPE_CODEC) diff --git a/apps/core_keymap.c b/apps/core_keymap.c index d9fa331727..cc7e00c428 100644 --- a/apps/core_keymap.c +++ b/apps/core_keymap.c @@ -37,7 +37,7 @@ static int open_key_remap(const char *filename, int *countp) if (fd < 0) return fd; - size_t fsize = filesize(fd); + size_t fsize = ffilesize(fd); int count = fsize / sizeof(struct button_mapping); if (count == 0 || (size_t)(count * sizeof(struct button_mapping)) != fsize) { diff --git a/apps/cuesheet.c b/apps/cuesheet.c index e30e5ca404..9b27c410dd 100644 --- a/apps/cuesheet.c +++ b/apps/cuesheet.c @@ -447,6 +447,30 @@ static const char* list_get_name_cb(int selected_item, return buffer; } +static char global_temp_buffer[MAX_PATH+1]; + +static int cuesheet_list_voice_cb(int list_index, void* data) +{ + struct cuesheet *cue = (struct cuesheet *) data; + int index = list_index / 2; + + talk_id(LANG_PLAYTIME_TRACK, true); + talk_number(index + 1, true); + + /* Look up and voice talk clips */ + const char *nameptr = NULL; + size_t dirlen = path_dirname(cue->file, &nameptr); + size_t tmp; + tmp = snprintf(global_temp_buffer, dirlen + 1, "%s", cue->file); + global_temp_buffer[tmp] = 0; + talk_file_or_spell(global_temp_buffer, cue->tracks[index].performer, + TALK_IDARRAY(LANG_ID3_ARTIST), true); + talk_file_or_spell(global_temp_buffer, cue->tracks[index].title, + TALK_IDARRAY(LANG_ID3_TITLE), true); + + return 0; +} + void browse_cuesheet(struct cuesheet *cue) { struct gui_synclist lists; @@ -463,11 +487,12 @@ void browse_cuesheet(struct cuesheet *cue) if ((unsigned) len > sizeof(title)) title[sizeof(title) - 2] = '~'; /* give indication of truncation */ - gui_synclist_init(&lists, list_get_name_cb, cue, false, 2, NULL); gui_synclist_set_nb_items(&lists, 2*cue->track_count); gui_synclist_set_title(&lists, title, 0); + if(global_settings.talk_menu) + gui_synclist_set_voice_callback(&lists, cuesheet_list_voice_cb); if (id3) { @@ -475,6 +500,8 @@ void browse_cuesheet(struct cuesheet *cue) 2*cue_find_current_track(cue, id3->elapsed)); } + gui_synclist_speak_item(&lists); + while (!done) { gui_synclist_draw(&lists); @@ -503,10 +530,9 @@ void browse_cuesheet(struct cuesheet *cue) if (!startit || !*cue->file) break; - char file[MAX_PATH]; - strmemccpy(file, cue->file, MAX_PATH); - char *fname = strrsplt(file, '/'); - char *dirname = fname <= file + 1 ? "/" : file; + strmemccpy(global_temp_buffer, cue->file, MAX_PATH); + char *fname = strrsplt(global_temp_buffer, '/'); + char *dirname = fname <= global_temp_buffer + 1 ? "/" : global_temp_buffer; bookmark_play(dirname, 0, elapsed, 0, current_tick, fname); break; } /* ACTION_STD_OK */ diff --git a/apps/debug_menu.c b/apps/debug_menu.c index 2a0f182ed2..c98d54ad64 100644 --- a/apps/debug_menu.c +++ b/apps/debug_menu.c @@ -142,7 +142,7 @@ #include "iap.h" #endif -#ifdef HIBY_LINUX +#if defined(HIBY_LINUX) && !defined(SIMULATOR) #include #endif @@ -1405,6 +1405,24 @@ static int disk_callback(int btn, struct gui_synclist *lists) i_vmin[card_extract_bits(card->csd, 55, 3)], i_vmax[card_extract_bits(card->csd, 52, 3)]); } +#if (CONFIG_STORAGE & STORAGE_SD) + /* + * Most drivers don't read SCR so an all zero value means + * the driver didn't populate it. Valid SCRs will have at + * least the SD_BUS_WIDTHS field containing a nonzero value. + */ + if (card->scr[0] || card->scr[1]) + { + int scr_vers = (card->scr[1] >> 28) & 0xF; + simplelist_addline("SCR version: %d", scr_vers); + + if (scr_vers == 0) + { + bool supports_cmd23 = card->scr[1] & 0x2; + simplelist_addline("CMD23 supported: %s", supports_cmd23 ? "yes" : "no"); + } + } +#endif } else if (card->initialized == 0) { @@ -2537,20 +2555,15 @@ static bool dbg_talk(void) #ifdef HAVE_USBSTACK #if (defined(ROCKBOX_HAS_LOGF) && defined(USB_ENABLE_SERIAL)) -static bool toggle_usb_core_driver(int driver, char *msg) -{ - bool enabled = !usb_core_driver_enabled(driver); - - usb_core_enable_driver(driver,enabled); - splashf(HZ, "%s %s", msg, enabled ? "enabled" : "disabled"); - - return false; -} - #ifdef USB_ENABLE_SERIAL static bool toggle_usb_serial(void) { - return toggle_usb_core_driver(USB_DRIVER_SERIAL, "USB Serial"); + bool enabled = !usb_get_serial(); + + usb_set_serial(enabled); + splashf(HZ, "USB Serial %s", enabled ? "enabled" : "disabled"); + + return false; } #endif /* USB_ENABLE_SERIAL */ #endif @@ -2795,7 +2808,7 @@ static bool dbg_bootflash_dump(void) { } #endif -#ifdef HIBY_LINUX +#if defined(HIBY_LINUX) && !defined(SIMULATOR) static bool view_ram_info(void) { struct simplelist_info info; @@ -2844,6 +2857,77 @@ static bool view_ram_info(void) } #endif +#if (CONFIG_PLATFORM & PLATFORM_NATIVE) && defined(CONFIG_NAND) \ + && (CONFIG_NAND == NAND_RK27XX) \ + && !(CONFIG_STORAGE & STORAGE_NAND) +#include "ftl-probe-rk27xx.h" + +/* The target does not know its NAND's FTL scheme yet: show what the + * finder sees, for the user to report */ +static bool dbg_ftl_scheme(void) +{ + struct ftl_probe p; + struct simplelist_info info; + + ftl_probe(&p); + simplelist_info_init(&info, "FTL scheme", 0, NULL); + simplelist_reset_lines(); + simplelist_addline("Scheme: %s", ftl_probe_scheme_name(p.scheme)); + if (p.flash_error) + { + simplelist_addline("No usable NAND (%d)", p.flash_error); + } + else + { + simplelist_addline("Chip: %lu blocks", (unsigned long)p.blocks); + simplelist_addline("%u planes, %u/%u sec", p.planes, p.sec_per_page, + p.sec_per_block); + if (p.idb_boot_blocks) + { + simplelist_addline("IDB: %u blk %u+%u MB", p.idb_boot_blocks, + p.idb_sys_mb, p.idb_data_mb); + if (p.idb_rk27) + simplelist_addline("IDB: RK27, ECC t=%u", p.idb_ecc_t); + else + simplelist_addline("IDB: early layout"); + } + else + simplelist_addline("IDB: none"); + simplelist_addline("Scanned %u blocks, t=%u", p.scanned, p.scan_ecc_t); + simplelist_addline("Erased %u, bad ECC %u", p.erased, p.unreadable); + simplelist_addline("A: %u logs, %u used", p.a_logs, p.a_blocks); + simplelist_addline("B: %u tables at %u", p.b_tables, + p.b_tables ? p.first_b_table : 0); + simplelist_addline("B: %u cache, %u data", p.b_cache, p.b_data); + if (p.b_other) + simplelist_addline("B: %u other, %04x", p.b_other, p.first_b_other); + if (p.other) + simplelist_addline("Other: %u, %u %02x%02x%02x", p.other, + p.first_other, p.first_other_meta[0], + p.first_other_meta[1], p.first_other_meta[2]); + } + return simplelist_show_list(&info); +} +#endif + +#if (CONFIG_PLATFORM & PLATFORM_NATIVE) && defined(CONFIG_NAND) \ + && (CONFIG_NAND == NAND_RK27XX) \ + && (CONFIG_STORAGE & STORAGE_NAND) +#include "ftl-target.h" +#endif + +#ifdef FTL_SYS_ON_USB +/* The NAND's hidden SYS volume, shown to a USB host in place of USER for + * one connection */ +static bool dbg_nand_sys_usb(void) +{ + ftl_set_sys_on_usb(!ftl_get_sys_on_usb()); + splashf(HZ, "NAND SYS on next USB %sabled", + ftl_get_sys_on_usb() ? "en" : "dis"); + return false; +} +#endif + /****** The menu *********/ static const struct { unsigned char *desc; /* string or ID */ @@ -2883,7 +2967,7 @@ static const struct { #ifdef __linux__ { "View CPU stats", dbg_cpuinfo }, #endif -#ifdef HIBY_LINUX +#if defined(HIBY_LINUX) && !defined(SIMULATOR) { "View RAM info", view_ram_info }, #endif #if (CONFIG_BATTERY_MEASURE != 0) && !defined(SIMULATOR) @@ -2896,6 +2980,14 @@ static const struct { #if ((CONFIG_PLATFORM & PLATFORM_NATIVE) || defined(SONY_NWZ_LINUX) || defined(HIBY_LINUX) || defined(FIIO_M3K_LINUX)) && !defined(SIMULATOR) { "View HW info", dbg_hw_info }, #endif +#if (CONFIG_PLATFORM & PLATFORM_NATIVE) && defined(CONFIG_NAND) \ + && (CONFIG_NAND == NAND_RK27XX) \ + && !(CONFIG_STORAGE & STORAGE_NAND) + { "View FTL scheme", dbg_ftl_scheme }, +#endif +#ifdef FTL_SYS_ON_USB + { "NAND SYS on next USB", dbg_nand_sys_usb }, +#endif #if (CONFIG_PLATFORM & PLATFORM_NATIVE) { "View partitions", dbg_partitions }, #endif diff --git a/apps/features.txt b/apps/features.txt index 2d3ad26a73..68351afee3 100644 --- a/apps/features.txt +++ b/apps/features.txt @@ -82,6 +82,10 @@ lcd_sleep morse_input #endif +#if defined(HAVE_KBD_POINT_MODE) +kbd_point_mode +#endif + #if defined(HAVE_PITCHCONTROL) pitchscreen #endif @@ -313,10 +317,14 @@ codec_aac_he lto #endif -#if defined(USB_ENABLE_AUDIO) +#if defined(USB_ENABLE_AUDIO) || defined(HAVE_HOST_USB_AUDIO) usbdac #endif #if defined(UTF8PROC_EXPORTS) utf8proc #endif + +#if defined(HAVE_COMPOSITE_VIDEO_OUT) +videoout +#endif diff --git a/apps/filetypes.c b/apps/filetypes.c index 30a1582da8..5578026bbf 100644 --- a/apps/filetypes.c +++ b/apps/filetypes.c @@ -72,6 +72,8 @@ static const struct filetype_inbuilt inbuilt_filetypes[] = { { "m3u8", FILE_ATTR_M3U }, { "cfg", FILE_ATTR_CFG }, { "wps", FILE_ATTR_WPS }, + { "bmp", FILE_ATTR_BMP }, + { "mod", FILE_ATTR_MOD }, #ifdef HAVE_REMOTE_LCD { "rwps", FILE_ATTR_RWPS }, #endif @@ -380,7 +382,7 @@ static void read_viewers_config_init(void) if(fd < 0) return; - off_t filesz = filesize(fd); + off_t filesz = ffilesize(fd); if(filesz <= 0) goto out; diff --git a/apps/gesture.c b/apps/gesture.c index 752f007896..c12d3b7593 100644 --- a/apps/gesture.c +++ b/apps/gesture.c @@ -180,3 +180,59 @@ bool gesture_vel_get(struct gesture_vel *gv, int *xvel, int *yvel) return gv->cnt >= ARRAYLEN(gv->xsamp); } + +/* + * flick detector + */ + +int gesture_flick_get_in_vp(const struct gesture_event *gevt, + const struct viewport *vp) +{ + const int vp_height = vp ? vp->height : LCD_HEIGHT; + const int vp_width = vp ? vp->width : LCD_WIDTH; + + /* margin at edge where flicks must originate */ + const int margin = touchscreen_get_scroll_threshold() * 2; + + /* minimum distance from edge before a flick is reported */ + const int x_dist = 1 + vp_width/4; + const int y_dist = 1 + vp_height/4; + + /* a flick only triggers if the velocity along the main axis + * exceeds the minimum and the velocity on the other axis is + * below the maximum */ + const int xv_min = 2*vp_width; + const int xv_max = vp_width; + const int yv_min = 2*vp_height; + const int yv_max = vp_height; + + /* a flick is only detected if it occurs within this timeout */ + const long timeout = HZ/2; + + long dt = gevt->last_tick - gevt->start_tick; + if (dt <= 0 || dt > timeout) + return GESTURE_FLICK_NONE; + + int xvel = (gevt->x - gevt->ox) * HZ / dt; + int yvel = (gevt->y - gevt->oy) * HZ / dt; + + if (gevt->ox < margin && gevt->x >= x_dist && + xvel >= xv_min && abs(yvel) <= yv_max) + return GESTURE_FLICK_LEFT; + + if (gevt->ox >= LCD_WIDTH - margin && + gevt->x < LCD_WIDTH - x_dist && + xvel <= -xv_min && abs(yvel) <= yv_max) + return GESTURE_FLICK_RIGHT; + + if (gevt->oy < margin && gevt->y >= y_dist && + yvel >= yv_min && abs(xvel) <= xv_max) + return GESTURE_FLICK_TOP; + + if (gevt->oy >= LCD_HEIGHT - margin && + gevt->y < LCD_HEIGHT - y_dist && + yvel <= -yv_min && abs(xvel) < xv_max) + return GESTURE_FLICK_BOTTOM; + + return GESTURE_FLICK_NONE; +} diff --git a/apps/gesture.h b/apps/gesture.h index a4fb5428dc..948d037d1f 100644 --- a/apps/gesture.h +++ b/apps/gesture.h @@ -133,4 +133,24 @@ void gesture_vel_reset(struct gesture_vel *gv); void gesture_vel_process(struct gesture_vel *gv, const struct touchevent *ev); bool gesture_vel_get(struct gesture_vel *gv, int *xvel, int *yvel); +/* Flick detection */ +enum gesture_flick_id +{ + /* The enum refers to originating edge: GESTURE_FLICK_LEFT + * means a flick from the left edge moving rightward. */ + GESTURE_FLICK_NONE = 0, + GESTURE_FLICK_TOP, + GESTURE_FLICK_BOTTOM, + GESTURE_FLICK_LEFT, + GESTURE_FLICK_RIGHT, +}; + +int gesture_flick_get_in_vp(const struct gesture_event *gevt, + const struct viewport *vp); + +static inline int gesture_flick_get(const struct gesture_event *gevt) +{ + return gesture_flick_get_in_vp(gevt, NULL); +} + #endif /* _GESTURE_H_ */ diff --git a/apps/gui/bitmap/list.c b/apps/gui/bitmap/list.c index 74bdea2f64..c2e8896a3e 100644 --- a/apps/gui/bitmap/list.c +++ b/apps/gui/bitmap/list.c @@ -41,6 +41,7 @@ #include "statusbar-skinned.h" #include "debug.h" #include "line.h" +#include "fixedpoint.h" #define ICON_PADDING 1 #define ICON_PADDING_S "1" @@ -540,15 +541,15 @@ static int swipe_scroll(struct gui_synclist *gui_list, int delta) */ #define SIGN(a) ((a) < 0 ? -1 : 1) -/* these could possibly be configurable */ -/* the lower the smoother */ #define RELOAD_INTERVAL (HZ/25) -/* the higher the earler the list stops */ -#define DECELERATION (1000*RELOAD_INTERVAL/HZ) +#define RELOAD_INTERVAL_FP ((RELOAD_INTERVAL << LIST_KINETIC_FRACBITS) / HZ) struct kinetic_cb_data { struct gui_synclist *list; - int velocity; + long velocity; + long distance; + long scroll_duration; + long press_duration; }; struct kinetic { @@ -560,10 +561,42 @@ struct kinetic { static struct kinetic kinetic; static struct gesture_vel list_gvel; +/* + * NOTE: defaults were chosen for the Shanling Q1, and may need + * tuning for other devices + */ + +const struct list_kinetic_scroll_settings list_kinetic_scroll_accel_default = { + .a0 = 200 << LIST_KINETIC_FRACBITS, /* 200.0 */ + .a1 = (6 << LIST_KINETIC_FRACBITS) / 5, /* 1.2 */ +}; + +const struct list_kinetic_scroll_settings list_kinetic_scroll_brake_default = { + .a0 = 200 << LIST_KINETIC_FRACBITS, /* 200.0 */ + .a1 = (1 << LIST_KINETIC_FRACBITS) / 2, /* 0.5 */ +}; + +const struct list_kinetic_scroll_settings list_kinetic_scroll_decel_default = { + .a0 = 2000 << LIST_KINETIC_FRACBITS, /* 2000.0 */ + .a1 = (1 << LIST_KINETIC_FRACBITS) / 2, /* 0.5 */ + .delay = 125 * HZ / 1000, /* 125 ms */ +}; + +const struct list_kinetic_scroll_settings list_kinetic_scroll_press_default = { + .a0 = 35000 << LIST_KINETIC_FRACBITS, /* 35000.0 */ + .a1 = 4 << LIST_KINETIC_FRACBITS, /* 4.0 */ + .delay = 250 * HZ / 1000, /* 250 ms */ +}; + static void kinetic_stop_scrolling(struct kinetic *k, struct gui_synclist *list) { if (k->cb_data.list == list) + { + k->cb_data.list = NULL; + k->cb_data.velocity = 0; + k->cb_data.distance = 0; timeout_cancel(&k->tmo); + } } /* helper for gui/list.c to cancel scrolling if a normal button event comes */ @@ -576,32 +609,66 @@ void _gui_synclist_stop_kinetic_scrolling(struct gui_synclist *list) } } +static long kinetic_calc_accel(long input, long duration, + const struct list_kinetic_scroll_settings *param) +{ + if (duration < param->delay) + return 0; + + long x1 = input < 0 ? -input : input; + long r = fp_mul(param->a1, x1, LIST_KINETIC_FRACBITS) + + param->a0 + (1 << (LIST_KINETIC_FRACBITS - 1)); + + return SIGN(input) * r; +} + static int kinetic_callback(struct timeout *tmo) { struct kinetic_cb_data *data = (struct kinetic_cb_data*)tmo->data; struct gui_synclist *list = data->list; + int pixel_diff, action; /* deal with cancellation */ - if (list->scroll_mode != SCROLL_KINETIC) + if (!list || list->scroll_mode != SCROLL_KINETIC) return 0; - /* ds = v*dt */ - int pixel_diff = data->velocity * RELOAD_INTERVAL / HZ; - int action = swipe_scroll(list, pixel_diff); + long abs_vel = data->velocity < 0 ? -data->velocity : data->velocity; + long vel_sgn = SIGN(data->velocity); + + data->distance += fp_mul(abs_vel, RELOAD_INTERVAL_FP, LIST_KINETIC_FRACBITS); + data->scroll_duration += RELOAD_INTERVAL; + + pixel_diff = data->distance >> LIST_KINETIC_FRACBITS; + data->distance -= pixel_diff << LIST_KINETIC_FRACBITS; + + action = swipe_scroll(list, pixel_diff * vel_sgn); if (action == ACTION_REDRAW) { /* force the list to redraw */ button_queue_post(BUTTON_REDRAW, 0); } - /* apply deceleration */ - int old_sign = SIGN(data->velocity); - data->velocity -= SIGN(data->velocity) * DECELERATION; - if (SIGN(data->velocity) != old_sign) + /* calculate and apply deceleration */ + long abs_decel = 0; + abs_decel += kinetic_calc_accel(abs_vel, data->scroll_duration, + &global_settings.kinetic_scroll_decel); + abs_decel += kinetic_calc_accel(abs_vel, data->press_duration, + &global_settings.kinetic_scroll_press); + abs_decel = fp_mul(abs_decel, RELOAD_INTERVAL_FP, LIST_KINETIC_FRACBITS); + + /* + * Ensure velocity is smoothly reduced to zero to avoid jerky + * scrolling near the end of scrolling. + */ + if (abs_vel < (3 << LIST_KINETIC_FRACBITS)) data->velocity = 0; + else if (abs_vel < abs_decel) + data->velocity /= 3; + else + data->velocity -= SIGN(data->velocity) * abs_decel; /* stop scrolling if we didn't move, it means we hit the end */ - if (list->y_pos == list->scroll_base_y) + if (list->y_pos == list->scroll_base_y && pixel_diff != 0) data->velocity = 0; else /* update base y since our scroll distance doesn't accumulate. */ @@ -624,8 +691,31 @@ static bool kinetic_start_scrolling(struct kinetic *k, struct gui_synclist *list if (yvel == 0) return false; + long yvel_fp = yvel << LIST_KINETIC_FRACBITS; + int vel_sgn = SIGN(k->cb_data.velocity); + + if (list->scroll_mode == SCROLL_KINETIC) + { + long accel; + if (SIGN(yvel) == vel_sgn) + accel = kinetic_calc_accel(k->cb_data.velocity, LONG_MAX, + &global_settings.kinetic_scroll_accel); + else + accel = -kinetic_calc_accel(k->cb_data.velocity, LONG_MAX, + &global_settings.kinetic_scroll_brake); + + yvel_fp += accel; + } + k->cb_data.list = list; - k->cb_data.velocity = yvel; + k->cb_data.velocity += yvel_fp; + /* also reset distance & scroll duration after a direction change */ + if (list->scroll_mode != SCROLL_KINETIC || + vel_sgn != SIGN(k->cb_data.velocity)) + { + k->cb_data.distance = 0; + k->cb_data.scroll_duration = 0; + } list->scroll_mode = SCROLL_KINETIC; list->scroll_base_y = list->y_pos; @@ -709,6 +799,25 @@ static int get_click_location(struct gui_synclist *list, int x, int y) return retval; } + +static int list_do_flick(const struct gesture_event *gevent) +{ + if (!global_settings.touch_enable_flick_shortcuts) + return ACTION_NONE; + + switch (gesture_flick_get(gevent)) + { + case GESTURE_FLICK_TOP: + return ACTION_STD_QUICKSCREEN; + case GESTURE_FLICK_LEFT: + return ACTION_STD_CANCEL; + case GESTURE_FLICK_RIGHT: + return ACTION_TREE_WPS; + default: + return ACTION_NONE; + } +} + unsigned gui_synclist_do_touchscreen(struct gui_synclist *list) { struct touchevent tevent; @@ -728,6 +837,11 @@ unsigned gui_synclist_do_touchscreen(struct gui_synclist *list) int action = ACTION_NONE; int click_loc; + if (action_gesture_is_pressed()) + kinetic.cb_data.press_duration = gevent.last_tick - gevent.start_tick; + else + kinetic.cb_data.press_duration = -1; + switch (gevent.id) { case GESTURE_NONE: @@ -737,7 +851,15 @@ unsigned gui_synclist_do_touchscreen(struct gui_synclist *list) case GESTURE_TAP: case GESTURE_LONG_PRESS: - _gui_synclist_stop_kinetic_scrolling(list); + /* In kinetic mode taps and presses only decelerate scrolling. + * User needs to wait until scrolling stops to select an item. */ + if (list->scroll_mode == SCROLL_KINETIC) + { + if (gevent.id != GESTURE_NONE) + gesture_vel_reset(&list_gvel); + break; + } + click_loc = get_click_location(list, gevent.x, gevent.y); if (click_loc & LIST) { @@ -789,17 +911,24 @@ unsigned gui_synclist_do_touchscreen(struct gui_synclist *list) break; case GESTURE_DRAGSTART: - _gui_synclist_stop_kinetic_scrolling(list); gesture_vel_reset(&list_gvel); - list->scroll_base_y = list->y_pos; /* fallthrough */ case GESTURE_DRAG: gesture_vel_process(&list_gvel, &tevent); + action = list_do_flick(&gevent); + if (action != ACTION_NONE) + { + action_gesture_reset(); + break; + } + if (list->scroll_mode == SCROLL_NONE) { + list->scroll_base_y = list->y_pos; click_loc = get_click_location(list, gevent.ox, gevent.oy); + if (click_loc & SCROLLBAR) list->scroll_mode = SCROLL_BAR; else if (click_loc & LIST) @@ -810,15 +939,14 @@ unsigned gui_synclist_do_touchscreen(struct gui_synclist *list) action = scrollbar_scroll(list, gevent.y); else if (list->scroll_mode == SCROLL_SWIPE) action = swipe_scroll(list, gevent.y - gevent.oy); - break; case GESTURE_RELEASE: - if (list->scroll_mode != SCROLL_SWIPE || - !kinetic_start_scrolling(&kinetic, list)) - { + if (list->scroll_mode == SCROLL_BAR) + list->scroll_mode = SCROLL_NONE; + else if(!kinetic_start_scrolling(&kinetic, list) && + list->scroll_mode != SCROLL_KINETIC) list->scroll_mode = SCROLL_NONE; - } action_gesture_reset(); action = ACTION_REDRAW; diff --git a/apps/gui/list.h b/apps/gui/list.h index ad31180c4f..faa5755b77 100644 --- a/apps/gui/list.h +++ b/apps/gui/list.h @@ -25,7 +25,9 @@ #include "action.h" #include "icon.h" #include "screen_access.h" +#ifndef __PCTOOL__ #include "skin_engine/skin_engine.h" +#endif #include "line.h" #define SCROLLBAR_WIDTH global_settings.scrollbar_width @@ -189,6 +191,41 @@ struct gui_synclist #endif }; +#ifdef HAVE_TOUCHSCREEN +#define LIST_KINETIC_FRACBITS 8 + +struct list_kinetic_scroll_settings +{ + /* Coefficients for a polynomial a1*x + a0, in fixed point. + * x is the kinetic scroll speed in pixels/sec (always positive) */ + long a0; + long a1; + + /* Delay (in ticks) during which the acceleration is not applied. */ + long delay; +}; + +/* + * accel: Extra impulse applied when swiping in the direction of scrolling, + * proportional to the current velocity. This is what allows for high + * scroll speeds. Delay is ignored. + * + * brake: Braking impulse applied when swiping in the opposite direction of + * scrolling, which helps to change direction quickly even at high + * speed. Delay is ignored. + * + * decel: Deceleration applied during scrolling. The delay is measured from + * the start of scrolling or last change of direction. + * + * press: Deceleration applied while the screen is pressed (motion is not + * needed to trigger). Additive with "decel". The delay is measured + * from the start of the press. + */ +extern const struct list_kinetic_scroll_settings list_kinetic_scroll_accel_default; +extern const struct list_kinetic_scroll_settings list_kinetic_scroll_brake_default; +extern const struct list_kinetic_scroll_settings list_kinetic_scroll_decel_default; +extern const struct list_kinetic_scroll_settings list_kinetic_scroll_press_default; +#endif extern void list_init(void); diff --git a/apps/gui/option_select.c b/apps/gui/option_select.c index 12dff945ad..d70af4d042 100644 --- a/apps/gui/option_select.c +++ b/apps/gui/option_select.c @@ -108,7 +108,12 @@ const char *option_get_valuestring(const struct settings_list *setting, if (formatter) str = formatter(buffer, buf_len, (int)temp_var, str_unit); else if (!is_time_setting) - snprintf(buffer, buf_len, "%d %s", (int)temp_var, str_unit?str_unit:""); + { + if (str_unit && str_unit[0]) + snprintf(buffer, buf_len, "%d %s", (int)temp_var, str_unit); + else + snprintf(buffer, buf_len, "%d", (int)temp_var); + } } else if (HASFLAG(setting, F_T_SOUND)) { diff --git a/apps/gui/quickscreen.c b/apps/gui/quickscreen.c index bc245dd80a..a04c4bb2cd 100644 --- a/apps/gui/quickscreen.c +++ b/apps/gui/quickscreen.c @@ -18,37 +18,34 @@ * ****************************************************************************/ -#include #include "config.h" -#include "system.h" #include "icons.h" #include "font.h" -#include "kernel.h" #include "misc.h" -#include "sound.h" #include "action.h" #include "settings_list.h" #include "lang.h" -#include "playlist.h" #include "viewport.h" -#include "audio.h" #include "quickscreen.h" #include "talk.h" -#include "list.h" #include "option_select.h" -#include "debug.h" -#include "shortcuts.h" #include "appevents.h" #include "statusbar-skinned.h" - /* 1 top, 1 bottom, 2 on either side, 1 for the icons - * if enough space, top and bottom have 2 lines */ -#define MIN_LINES 5 + /* 2 lines each for the top, middle, and bottom section = 6 in total. + With less space, top and bottom sections lose a line = 4 in total. */ +#define MIN_LINES (3*2) + #define MAX_NEEDED_LINES 10 - /* pixels between the 2 center items minimum or between text and icons, - * and between text and parent boundaries */ + + /* Minimum number of pixels between the 2 center items, between + text and icons, or between text and parent boundaries. */ #define MARGIN 10 -#define CENTER_ICONAREA_SIZE (MARGIN+8*2) +#define CENTER_ICONAREA_SIZE (MARGIN + 8*2) +#define QS_ICON_WIDTH 7 +#define QS_ICON_HEIGHT 8 + +#define FOR_QS_ITEMS(i) for (int i = 0; i < QUICKSCREEN_ITEM_COUNT; i++) struct quickscreen { @@ -58,126 +55,186 @@ struct quickscreen struct viewport vp_icons[NB_SCREENS]; int button_enter; enum quickscreen_return result; + enum quickscreen_item volume_item; }; -static void quickscreen_fix_viewports(struct quickscreen *qs, enum screen_type screen) +/* Skin draws custom Quickscreen UI */ +static bool qs_skinned[NB_SCREENS]; + +/* Toggle built-in interface, based on + whether skin draws custom QS UI. */ +void quickscreen_set_skinned(enum screen_type screen, bool skinned) { - int char_height, width, pad = 0; - int left_width = 0, right_width = 0, vert_lines; - unsigned char *s; - struct screen *display = &screens[screen]; - struct viewport *parent = &qs->parent[screen]; - struct viewport *vps = qs->vps[screen]; - struct viewport *vp_icons = &qs->vp_icons[screen]; - int nb_lines = viewport_get_nb_lines(parent); + qs_skinned[screen] = skinned; +} - /* nb_lines only returns the number of fully visible lines, small screens - or really large fonts could cause problems with the calculation below. - */ - if (nb_lines == 0) - nb_lines++; +/* Set up icons viewport */ +static inline void setup_icons(struct viewport *vp_icons, struct viewport *vps) +{ + vp_icons->x = vps[QUICKSCREEN_LEFT].x + vps[QUICKSCREEN_LEFT].width; + vp_icons->y = vps[QUICKSCREEN_TOP].y + vps[QUICKSCREEN_TOP].height; - char_height = parent->height/nb_lines; - - /* center the icons VP first */ - *vp_icons = *parent; - vp_icons->width = CENTER_ICONAREA_SIZE; /* abosulte smallest allowed */ - vp_icons->x = parent->x; - vp_icons->x += (parent->width-CENTER_ICONAREA_SIZE)/2; - - vps[QUICKSCREEN_BOTTOM] = *parent; - vps[QUICKSCREEN_TOP] = *parent; - /* depending on the space the top/buttom items use 1 or 2 lines */ - if (nb_lines < MIN_LINES) - vert_lines = 1; - else - vert_lines = 2; - vps[QUICKSCREEN_TOP].y = parent->y; - vps[QUICKSCREEN_TOP].height = vps[QUICKSCREEN_BOTTOM].height - = vert_lines*char_height; - vps[QUICKSCREEN_BOTTOM].y - = parent->y + parent->height - vps[QUICKSCREEN_BOTTOM].height; - - /* enough space vertically, so put a nice margin */ - if (nb_lines >= MAX_NEEDED_LINES) - { - vps[QUICKSCREEN_TOP].y += MARGIN; - vps[QUICKSCREEN_BOTTOM].y -= MARGIN; - } - - vp_icons->y = vps[QUICKSCREEN_TOP].y - + vps[QUICKSCREEN_TOP].height; + vp_icons->width = vps[QUICKSCREEN_RIGHT].x - vp_icons->x; vp_icons->height = vps[QUICKSCREEN_BOTTOM].y - vp_icons->y; - /* adjust the left/right items widths to fit the screen nicely */ - if (qs->items[QUICKSCREEN_LEFT]) - { - s = P2STR(ID2P(qs->items[QUICKSCREEN_LEFT]->lang_id)); - left_width = display->getstringsize(s, NULL, NULL); - } - if (qs->items[QUICKSCREEN_RIGHT]) - { - s = P2STR(ID2P(qs->items[QUICKSCREEN_RIGHT]->lang_id)); - right_width = display->getstringsize(s, NULL, NULL); - } - - width = MAX(left_width, right_width); - if (width*2 + vp_icons->width > parent->width) - { /* crop text viewports */ - width = (parent->width - vp_icons->width)/2; - } - else - { /* add more gap in icons vp */ - int excess = parent->width - vp_icons->width - width*2; - if (excess > MARGIN*4) - { - pad = MARGIN; - excess -= MARGIN*2; - } - vp_icons->x -= excess/2; - vp_icons->width += excess; - } - - vps[QUICKSCREEN_LEFT] = *parent; - vps[QUICKSCREEN_LEFT].x = parent->x + pad; - vps[QUICKSCREEN_LEFT].width = width; - - vps[QUICKSCREEN_RIGHT] = *parent; - vps[QUICKSCREEN_RIGHT].x = parent->x + parent->width - width - pad; - vps[QUICKSCREEN_RIGHT].width = width; - - vps[QUICKSCREEN_LEFT].height = vps[QUICKSCREEN_RIGHT].height - = 2*char_height; - - vps[QUICKSCREEN_LEFT].y = vps[QUICKSCREEN_RIGHT].y - = parent->y + (parent->height/2) - char_height; - - /* shrink the icons vp by a few pixels if there is room so the arrows - aren't drawn right next to the text */ + /* Shrink icons vp by a few pixels if there is room, + so the arrows aren't drawn right next to the text */ if (vp_icons->width > CENTER_ICONAREA_SIZE*2) { + vp_icons->x += CENTER_ICONAREA_SIZE*2/6; vp_icons->width -= CENTER_ICONAREA_SIZE*2/3; - vp_icons->x += CENTER_ICONAREA_SIZE*2/6; } if (vp_icons->height > CENTER_ICONAREA_SIZE*2) { + vp_icons->y += CENTER_ICONAREA_SIZE*2/6; vp_icons->height -= CENTER_ICONAREA_SIZE*2/3; - vp_icons->y += CENTER_ICONAREA_SIZE*2/6; } - - /* text alignment */ - vps[QUICKSCREEN_LEFT].flags &= ~VP_FLAG_ALIGNMENT_MASK; /* left-aligned */ - vps[QUICKSCREEN_TOP].flags |= VP_FLAG_ALIGN_CENTER; /* centered */ - vps[QUICKSCREEN_BOTTOM].flags |= VP_FLAG_ALIGN_CENTER; /* centered */ - vps[QUICKSCREEN_RIGHT].flags &= ~VP_FLAG_ALIGNMENT_MASK;/* right aligned*/ - vps[QUICKSCREEN_RIGHT].flags |= VP_FLAG_ALIGN_RIGHT; } +/* Set y-position and height for all text viewports */ +static inline void set_y_axis(struct viewport *left, struct viewport *right, + struct viewport *top, struct viewport *bottom) +{ + int parent_height = top->height; + /* Displayable lines in UI vp. 1 if no full line can be displayed */ + int nb_lines = viewport_get_nb_lines(top) ?: 1; + int line_height = parent_height/nb_lines; + + /* Top and bottom use 2 lines each, only if there's enough space */ + top->height = line_height; + if (nb_lines >= MIN_LINES) + top->height *= 2; + bottom->height = top->height; + bottom->y += parent_height - bottom->height; + + if (nb_lines >= MAX_NEEDED_LINES) /* more than enough vertical space */ + { + top->y += MARGIN; + bottom->y -= MARGIN; + } + if (nb_lines >= 2) /* Single line uses parent height and y position */ + { + left->height = right->height = 2*line_height; + right->y += (parent_height/2) - line_height; + left->y = right->y; + } +} + +/* Set x-position and width for left and right text viewport */ +static inline void set_x_axis(struct viewport *left, struct viewport *right, + int width) +{ + int parent_width = left->width; + int remaining_width = parent_width - width*2 - CENTER_ICONAREA_SIZE; + + if (remaining_width < 0) + { + /* Shrink left and right text vps */ + width = parent_width; + if (width > CENTER_ICONAREA_SIZE) /* Check if icons fit */ + width -= CENTER_ICONAREA_SIZE; + else if (width > MARGIN) /* Check if margin without icons fits */ + width -= MARGIN; + + if (width >= 2) + width /= 2; + } + else if (remaining_width > MARGIN*4) + { + left->x += MARGIN; + right->x -= MARGIN; + } + + right->x += parent_width - width; + right->width = left->width = width; +} + +/* Set up all QS viewports */ +static void quickscreen_setup_viewports(struct quickscreen *qs, + enum screen_type screen) +{ + struct viewport *parent = &qs->parent[screen]; + struct viewport *vps = qs->vps[screen]; + int width = 0; + + qs->vp_icons[screen] = *parent; + FOR_QS_ITEMS(i) + { + vps[i] = *parent; + vps[i].flags &= ~VP_FLAG_ALIGNMENT_MASK; + + /* Check maximum width needed for left or right text vp */ + if (!qs->items[i] || (i != QUICKSCREEN_LEFT && i != QUICKSCREEN_RIGHT)) + continue; + const char *str = P2STR(ID2P(qs->items[i]->lang_id)); + width = MAX(font_getstringsize(str, NULL, NULL, parent->font), width); + } + /* Set text alignment */ + vps[QUICKSCREEN_RIGHT].flags |= VP_FLAG_ALIGN_RIGHT; + vps[QUICKSCREEN_TOP].flags |= VP_FLAG_ALIGN_CENTER; + vps[QUICKSCREEN_BOTTOM].flags |= VP_FLAG_ALIGN_CENTER; + + /* Set x-position and width for left and right text viewports + (top and bottom use parent's entire width) */ + set_x_axis(&vps[QUICKSCREEN_LEFT], &vps[QUICKSCREEN_RIGHT], width); + + /* Set y-position and height for all text viewports */ + set_y_axis(&vps[QUICKSCREEN_LEFT], &vps[QUICKSCREEN_RIGHT], + &vps[QUICKSCREEN_TOP], &vps[QUICKSCREEN_BOTTOM]); + + /* Icons viewport fills center area */ + setup_icons(&qs->vp_icons[screen], vps); +} + +/* Draw settings item into current viewport */ +static void quickscreen_draw_setting(const struct settings_list *item, + struct screen *display, bool single_line) +{ + char buf[MAX_PATH]; + const char *title = P2STR(ID2P(item->lang_id)); + const char *value = option_get_valuestring(item, buf, sizeof buf, + option_value_as_int(item)); + if (single_line) + { + char text[MAX_PATH]; + snprintf(text, sizeof text, "%s: %s", title, value); + display->puts_scroll(0, 0, text); + } + else + { + display->puts_scroll(0, 0, title); + display->puts_scroll(0, 1, value); + } +} + +/* Redraw viewports affected by the adjusted setting */ +static void quickscreen_update(struct quickscreen *qs, enum quickscreen_item selected) +{ + FOR_NB_SCREENS(screen) + { + if (qs_skinned[screen]) + continue; + + struct screen *display = &screens[screen]; + struct viewport *vps = qs->vps[screen]; + + FOR_QS_ITEMS(i) + if (qs->items[i] == qs->items[selected]) + { + struct viewport *last_vp = display->set_viewport(&vps[i]); + display->clear_viewport(); + quickscreen_draw_setting(qs->items[i], display, + viewport_get_nb_lines(&vps[i]) < 2); + display->set_viewport(last_vp); + } + + skin_mark_dirty(screen); + } +} + +/* Redraw whole Quickscreen */ static void quickscreen_draw(struct quickscreen *qs, enum screen_type screen) { - int temp, i; - char buf[MAX_PATH]; - unsigned const char *title, *value; struct screen *display = &screens[screen]; struct viewport *parent = &qs->parent[screen]; struct viewport *vps = qs->vps[screen]; @@ -185,55 +242,45 @@ static void quickscreen_draw(struct quickscreen *qs, enum screen_type screen) struct viewport *last_vp = display->set_viewport(parent); display->clear_viewport(); - for (i = 0; i < QUICKSCREEN_ITEM_COUNT; i++) - { - struct viewport *vp = &vps[i]; - if (!qs->items[i]) - continue; - display->set_viewport(vp); - - title = P2STR(ID2P(qs->items[i]->lang_id)); - temp = option_value_as_int(qs->items[i]); - value = option_get_valuestring(qs->items[i], - buf, sizeof buf, temp); - - if (viewport_get_nb_lines(vp) < 2) + FOR_QS_ITEMS(i) + if (qs->items[i]) { - char text[MAX_PATH]; - snprintf(text, sizeof text, "%s: %s", title, value); - display->puts_scroll(0, 0, text); + display->set_viewport(&vps[i]); + quickscreen_draw_setting(qs->items[i], display, + viewport_get_nb_lines(&vps[i]) < 2); } - else - { - display->puts_scroll(0, 0, title); - display->puts_scroll(0, 1, value); - } - } - /* draw the icons */ - display->set_viewport(vp_icons); - if (qs->items[QUICKSCREEN_TOP] != NULL) + /* icons */ + if (parent->width > CENTER_ICONAREA_SIZE && vp_icons->height >= QS_ICON_HEIGHT) { - display->mono_bitmap(bitmap_icons_7x8[Icon_UpArrow], - (vp_icons->width/2) - 4, 0, 7, 8); - } - if (qs->items[QUICKSCREEN_RIGHT] != NULL) - { - display->mono_bitmap(bitmap_icons_7x8[Icon_FastForward], - vp_icons->width - 8, (vp_icons->height/2) - 4, 7, 8); - } - if (qs->items[QUICKSCREEN_LEFT] != NULL) - { - display->mono_bitmap(bitmap_icons_7x8[Icon_FastBackward], - 0, (vp_icons->height/2) - 4, 7, 8); - } - if (qs->items[QUICKSCREEN_BOTTOM] != NULL) - { - display->mono_bitmap(bitmap_icons_7x8[Icon_DownArrow], - (vp_icons->width/2) - 4, vp_icons->height - 8, 7, 8); + display->set_viewport(vp_icons); + + if (qs->items[QUICKSCREEN_LEFT]) + display->mono_bitmap(bitmap_icons_7x8[Icon_FastBackward], + 0, + vp_icons->height/2 - QS_ICON_HEIGHT/2, + QS_ICON_WIDTH, QS_ICON_HEIGHT); + + if (qs->items[QUICKSCREEN_RIGHT]) + display->mono_bitmap(bitmap_icons_7x8[Icon_FastForward], + vp_icons->width - QS_ICON_WIDTH, + vp_icons->height/2 - QS_ICON_HEIGHT/2, + QS_ICON_WIDTH, QS_ICON_HEIGHT); + + if (qs->items[QUICKSCREEN_TOP]) + display->mono_bitmap(bitmap_icons_7x8[Icon_UpArrow], + vp_icons->width/2 - QS_ICON_WIDTH/2, + 0, + QS_ICON_WIDTH, QS_ICON_HEIGHT); + + if (qs->items[QUICKSCREEN_BOTTOM]) + display->mono_bitmap(bitmap_icons_7x8[Icon_DownArrow], + vp_icons->width/2 - QS_ICON_WIDTH/2, + vp_icons->height - QS_ICON_HEIGHT, + QS_ICON_WIDTH, QS_ICON_HEIGHT); } - skin_mark_dirty(display->screen_type); + skin_mark_dirty(screen); display->set_viewport(last_vp); } @@ -243,7 +290,8 @@ static void quickscreen_draw_cb(unsigned short id, void *data, void *userdata) (void)data; FOR_NB_SCREENS(i) - quickscreen_draw((struct quickscreen *) userdata, i); + if (!qs_skinned[i]) + quickscreen_draw((struct quickscreen *) userdata, i); } static void talk_qs_option(const struct settings_list *opt, bool enqueue) @@ -262,39 +310,39 @@ static void talk_qs_option(const struct settings_list *opt, bool enqueue) * - button : the key we are going to analyse * returns : true if the button corresponded to an action, false otherwise */ -static bool quickscreen_do_button(struct quickscreen * qs, int button) +static bool quickscreen_do_button(struct quickscreen * qs, int button, + enum quickscreen_item *item) { - int item; bool previous = false; switch(button) { case ACTION_QS_TOP: - item = QUICKSCREEN_TOP; + *item = QUICKSCREEN_TOP; break; case ACTION_QS_LEFT: - item = QUICKSCREEN_LEFT; + *item = QUICKSCREEN_LEFT; previous = true; break; case ACTION_QS_DOWN: - item = QUICKSCREEN_BOTTOM; + *item = QUICKSCREEN_BOTTOM; previous = true; break; case ACTION_QS_RIGHT: - item = QUICKSCREEN_RIGHT; + *item = QUICKSCREEN_RIGHT; break; default: return false; } - if (qs->items[item] == NULL) + if (qs->items[*item] == NULL) return false; - option_select_next_val(qs->items[item], previous, true); - talk_qs_option(qs->items[item], false); + option_select_next_val(qs->items[*item], previous, true); + talk_qs_option(qs->items[*item], false); return true; } @@ -342,6 +390,7 @@ static int quickscreen_touchscreen_button(void) } #endif +/* Undo activity, viewport, and event listener setup. */ static void cleanup(void *parameter) { struct quickscreen *qs = (struct quickscreen *) parameter; @@ -349,26 +398,17 @@ static void cleanup(void *parameter) FOR_NB_SCREENS(i) { - for (int j = 0; j < QUICKSCREEN_ITEM_COUNT; j++) - screens[i].scroll_stop_viewport(&qs->vps[i][j]); - viewportmanager_theme_undo(i, !(qs->result & QUICKSCREEN_GOTO_SHORTCUTS_MENU)); + if (!qs_skinned[i]) + FOR_QS_ITEMS(j) + screens[i].scroll_stop_viewport(&qs->vps[i][j]); + viewportmanager_theme_undo(i, true); } - /* Eliminate flashing of parent during transition to Shortcuts */ - if (qs->result & QUICKSCREEN_GOTO_SHORTCUTS_MENU) - pop_current_activity_without_refresh(); - else - pop_current_activity(); + pop_current_activity(); } -static void quickscreen_run(struct quickscreen * qs) +/* Set up activity, viewport, and event listener. Draw initial Quickscreen. */ +static inline void setup(struct quickscreen *qs) { - int button; - /* To quit we need either : - * - a second press on the button that made us enter - * - an action taken while pressing the enter button, - * then release the enter button*/ - bool can_quit = false; - push_current_activity(ACTIVITY_QUICKSCREEN); FOR_NB_SCREENS(i) @@ -376,9 +416,27 @@ static void quickscreen_run(struct quickscreen * qs) screens[i].set_viewport(NULL); screens[i].scroll_stop(); viewportmanager_theme_enable(i, true, &qs->parent[i]); - quickscreen_fix_viewports(qs, i); - quickscreen_draw(qs, i); + if (!qs_skinned[i]) + { + quickscreen_setup_viewports(qs, i); + quickscreen_draw(qs, i); + } } + add_event_ex(GUI_EVENT_NEED_UI_UPDATE, false, quickscreen_draw_cb, qs); +} + +static void quickscreen_main(struct quickscreen * qs) +{ + /* To quit we need either : + * - a second press on the button that made us enter + * - an action taken while pressing the enter button, + * then release the enter button */ + bool can_quit = false; + int button; + enum quickscreen_item item; + + setup(qs); + /* Announce current selection on entering this screen. This is all queued up, but can be interrupted as soon as a setting is changed. */ @@ -393,7 +451,6 @@ static void quickscreen_run(struct quickscreen * qs) #ifdef HAVE_TOUCHSCREEN action_gesture_reset(); #endif - add_event_ex(GUI_EVENT_NEED_UI_UPDATE, false, quickscreen_draw_cb, qs); while (true) { button = get_action(CONTEXT_QUICKSCREEN, HZ/5); @@ -407,22 +464,19 @@ static void quickscreen_run(struct quickscreen * qs) qs->result |= QUICKSCREEN_IN_USB; return; } - if (quickscreen_do_button(qs, button)) + if (quickscreen_do_button(qs, button, &item)) { qs->result |= QUICKSCREEN_CHANGED; can_quit = true; - FOR_NB_SCREENS(i) - quickscreen_draw(qs, i); + quickscreen_update(qs, item); } else if (button == qs->button_enter) can_quit = true; - else if (button == ACTION_QS_VOLUP) { - adjust_volume(1); - sb_skin_force_next_update(); - } - else if (button == ACTION_QS_VOLDOWN) { - adjust_volume(-1); - sb_skin_force_next_update(); + else if (button == ACTION_QS_VOLUP || button == ACTION_QS_VOLDOWN) + { + adjust_volume(button == ACTION_QS_VOLUP ? 1 : -1); + if (qs->volume_item < QUICKSCREEN_ITEM_COUNT) + quickscreen_update(qs, qs->volume_item); } else if (button == ACTION_STD_CONTEXT) { @@ -434,27 +488,37 @@ static void quickscreen_run(struct quickscreen * qs) if (button == ACTION_STD_CANCEL) break; + + /* Ignore SBS update_delay, so that setting + changes are immediately visible. */ + sb_skin_force_next_update(); } /* Notify that we're exiting this screen */ cond_talk_ids_fq(VOICE_OK); + cleanup(qs); } -int quick_screen_quick(int button_enter) +/* Entry point for external callers */ +int quickscreen_show(int button_enter) { struct quickscreen qs; qs.button_enter = button_enter; qs.result = QUICKSCREEN_OK; + qs.volume_item = QUICKSCREEN_ITEM_COUNT; - for (int i = 0; i < 4; ++i) + FOR_QS_ITEMS(i) { qs.items[i] = global_settings.qs_items[i]; if (!is_setting_quickscreenable(qs.items[i])) qs.items[i] = NULL; + + if (qs.items[i] && qs.items[i]->lang_id == LANG_VOLUME) + qs.volume_item = i; } - quickscreen_run(&qs); + quickscreen_main(&qs); if (qs.result & QUICKSCREEN_CHANGED) settings_save(); diff --git a/apps/gui/quickscreen.h b/apps/gui/quickscreen.h index 28a012669b..f59f0d3058 100644 --- a/apps/gui/quickscreen.h +++ b/apps/gui/quickscreen.h @@ -44,8 +44,8 @@ enum quickscreen_return { QUICKSCREEN_CHANGED = 0x4, }; -extern int quick_screen_quick(int button_enter); -int quickscreen_set_option(void *data); +int quickscreen_show(int button_enter); +void quickscreen_set_skinned(enum screen_type screen, bool skinned); bool is_setting_quickscreenable(const struct settings_list *setting); #endif /*_GUI_QUICK_SCREEN_H_*/ diff --git a/apps/gui/skin_engine/skin_display.c b/apps/gui/skin_engine/skin_display.c index c2c19b2b8c..f2435fe709 100644 --- a/apps/gui/skin_engine/skin_display.c +++ b/apps/gui/skin_engine/skin_display.c @@ -170,6 +170,34 @@ void ab_draw_markers(struct screen * screen, int capacity, #endif +/* A peak meter bar with the hold option: the level falls back by the peak + * release rate - pixels per tick, here of a bar of the given size - and + * the highest recent level is held for the peak hold time. */ +static unsigned long peak_bar_hold(struct progressbar *pb, unsigned long end, + int size) +{ + int release, hold_ticks; + long delta = current_tick - pb->last_tick; + int level = end; + + peak_meter_get_times(&release, &hold_ticks); + pb->last_tick = current_tick; + if (size <= 0) + size = 1; + if (delta >= 0 && delta < HZ) + level = MAX(level, pb->level - delta * release * MAX_PEAK / size); + pb->level = level; + + if (TIME_AFTER(current_tick, pb->hold_tick)) + pb->hold_pos = 0; + if (level > pb->hold_pos) + { + pb->hold_pos = level; + pb->hold_tick = current_tick + hold_ticks; + } + return level; +} + void draw_progressbar(struct gui_wps *gwps, struct skin_viewport* skin_viewport, int line, struct progressbar *pb) { @@ -178,7 +206,7 @@ void draw_progressbar(struct gui_wps *gwps, struct skin_viewport* skin_viewport, struct wps_state *state = get_wps_state(); struct mp3entry *id3 = state->id3; int x = pb->x, y = pb->y, width = pb->width, height = pb->height; - unsigned long length, end; + unsigned long length = 1, end = 0; int flags = HORIZONTAL; if (height < 0) @@ -221,11 +249,6 @@ void draw_progressbar(struct gui_wps *gwps, struct skin_viewport* skin_viewport, length = pl_total; end = pl_elapsed; } - else - { - length = 1; - end = 0; - } } else if (pb->type == SKIN_TOKEN_PEAKMETER_LEFTBAR || pb->type == SKIN_TOKEN_PEAKMETER_RIGHTBAR) @@ -235,6 +258,8 @@ void draw_progressbar(struct gui_wps *gwps, struct skin_viewport* skin_viewport, val = pb->type == SKIN_TOKEN_PEAKMETER_LEFTBAR ? left : right; length = MAX_PEAK; end = peak_meter_scale_value(val, length); + if (pb->hold) + end = peak_bar_hold(pb, end, pb->horizontal ? width : height); } else if (pb->type == SKIN_TOKEN_PLAYLIST_PERCENTBAR) { @@ -281,7 +306,8 @@ void draw_progressbar(struct gui_wps *gwps, struct skin_viewport* skin_viewport, length = id3->length; end = id3->elapsed + state->ff_rewind_count; } - else + + if (length <= 0) { length = 1; end = 0; @@ -349,6 +375,59 @@ void draw_progressbar(struct gui_wps *gwps, struct skin_viewport* skin_viewport, length, 0, end, flags); } + /* the held peak of a peak meter bar: a block hold_size pixels thick + * ending at the held level, cut from the fill image if the bar has one, + * so that a bar drawn as LED segments holds a segment */ + if (pb->hold && pb->hold_pos > 0 && + (pb->type == SKIN_TOKEN_PEAKMETER_LEFTBAR || + pb->type == SKIN_TOKEN_PEAKMETER_RIGHTBAR)) + { + struct gui_img *img = pb->nobar ? NULL : + SKINOFFSETTOPTR(get_skin_buffer(gwps->data), pb->image); + int hx = x, hy = y, hw = width, hh = height; + int len, pos, off, size = pb->hold_size; + + if (!img && !(flags&BORDER_NOFILL)) + { + /* inside the frame, like the bar */ + hx++; hy++; hw -= 2; hh -= 2; + } + len = (flags&HORIZONTAL) ? hw : hh; + pos = len * pb->hold_pos / MAX_PEAK; + off = pos - size; + if (off < 0) + { + size += off; + off = 0; + } + if (flags&INVERTFILL) + off = len - pos; + + if (flags&HORIZONTAL) + { + hx += off; + hw = size; + } + else + { + hy += off; + hh = size; + } + + if (img) + { + /* the image maps onto the bar 1:1, as in the bar itself */ + int bx = hx - x, by = hy - y; + img->bm.data = core_get_data(img->buflib_handle); + hw = MIN(hw, img->bm.width - bx); + hh = MIN(hh, img->bm.height - by); + if (hw > 0 && hh > 0) + display->bmp_part(&img->bm, bx, by, hx, hy, hw, hh); + } + else if (hw > 0 && hh > 0) + display->fillrect(hx, hy, hw, hh); + } + if (SKINOFFSETTOPTR(get_skin_buffer(gwps->data), pb->slider)) { int xoff = 0, yoff = 0; @@ -760,52 +839,35 @@ bool skin_has_sbs(struct gui_wps *gwps) return draw; } -/* do the button loop as often as required for the peak meters to update - * with a good refresh rate. - */ +/* Enter button loop updating peak meter at a high refresh rate */ int skin_wait_for_action(enum skinnable_screens skin, int context, int timeout) { - int button = ACTION_NONE; - /* when the peak meter is enabled we want to have a - few extra updates to make it look smooth. On the - other hand we don't want to waste energy if it - isn't displayed */ - bool pm=false; + /* Skip updates if peak meter disabled. */ + bool peak_meter_enabled = false; FOR_NB_SCREENS(i) + peak_meter_enabled |= skin_get_gwps(skin, i)->data->peak_meter_enabled; + if (!peak_meter_enabled) + return get_action(context, timeout); + + long next_refresh = current_tick; + long timeout_tick = current_tick + timeout; + + while (true) { - if(skin_get_gwps(skin, i)->data->peak_meter_enabled) - pm = true; + int action = get_action(context, TIMEOUT_NOBLOCK); + if (action != ACTION_NONE || !(TIME_BEFORE(current_tick, timeout_tick))) + return action; + + peak_meter_peek(); /* Read peak values */ + sleep(0); /* Sleep until end of current tick. */ + + if (TIME_BEFORE(current_tick, next_refresh)) + continue; + + FOR_NB_SCREENS(i) + if (skin_get_gwps(skin, i)->data->peak_meter_enabled) + skin_update(skin, i, SKIN_REFRESH_PEAK_METER); + + next_refresh += HZ/PEAK_METER_FPS; } - - if (pm) { - long next_refresh = current_tick; - long next_big_refresh = current_tick + timeout; - button = BUTTON_NONE; - while (TIME_BEFORE(current_tick, next_big_refresh)) { - button = get_action(context,TIMEOUT_NOBLOCK); - if (button != ACTION_NONE) { - break; - } - peak_meter_peek(); - sleep(0); /* Sleep until end of current tick. */ - - if (TIME_AFTER(current_tick, next_refresh)) { - FOR_NB_SCREENS(i) - { - if(skin_get_gwps(skin, i)->data->peak_meter_enabled) - skin_update(skin, i, SKIN_REFRESH_PEAK_METER); - next_refresh += HZ / PEAK_METER_FPS; - } - } - } - - } - - /* The peak meter is disabled - -> no additional screen updates needed */ - else - { - button = get_action(context, timeout); - } - return button; } diff --git a/apps/gui/skin_engine/skin_engine.c b/apps/gui/skin_engine/skin_engine.c index 61a0c65e02..e382fb65c0 100644 --- a/apps/gui/skin_engine/skin_engine.c +++ b/apps/gui/skin_engine/skin_engine.c @@ -203,22 +203,22 @@ void settings_apply_skins(void) skin_backdrop_show(sb_get_backdrop(i)); } -void skin_load(enum skinnable_screens skin, enum screen_type screen, - const char *buf, bool isfile) +static void skin_load(enum skinnable_screens skin, enum screen_type screen, + const char *filename) { bool loaded = false; skin_helpers[skin]->process(screen, &skins[skin][screen].data, true); - if (buf && *buf) - loaded = skin_data_load(screen, &skins[skin][screen].data, buf, isfile, - &skins[skin][screen].stats); + if (filename && *filename) + loaded = skin_data_load(skin, screen, &skins[skin][screen].data, + filename, true, &skins[skin][screen].stats); if (!loaded && skin_helpers[skin]->default_skin) { - loaded = skin_data_load(screen, &skins[skin][screen].data, - skin_helpers[skin]->default_skin(screen), false, - &skins[skin][screen].stats); + loaded = skin_data_load(skin, screen, &skins[skin][screen].data, + skin_helpers[skin]->default_skin(screen), + false, &skins[skin][screen].stats); skins[skin][screen].failsafe_loaded = loaded; } @@ -230,8 +230,8 @@ void skin_load(enum skinnable_screens skin, enum screen_type screen, #endif } -static char* get_skin_filename(char *buf, size_t buf_size, - enum skinnable_screens skin, enum screen_type screen) +static char* get_skin_filename(enum skinnable_screens skin, enum screen_type screen, + char *buf, size_t buf_size) { (void)screen; char *setting = NULL, *ext = NULL; @@ -300,10 +300,10 @@ struct gui_wps *skin_get_gwps(enum skinnable_screens skin, enum screen_type scre if (skins[skin][screen].data.wps_loaded == false) { - char filename[MAX_PATH]; - char *buf = get_skin_filename(filename, MAX_PATH, skin, screen); + char buf[MAX_PATH]; + const char *filename = get_skin_filename(skin, screen, buf, sizeof buf); cpu_boost(true); - skin_load(skin, screen, buf, true); + skin_load(skin, screen, filename); cpu_boost(false); } return &skins[skin][screen].gui_wps; diff --git a/apps/gui/skin_engine/skin_engine.h b/apps/gui/skin_engine/skin_engine.h index f06c4c86f0..363252794f 100644 --- a/apps/gui/skin_engine/skin_engine.h +++ b/apps/gui/skin_engine/skin_engine.h @@ -67,14 +67,9 @@ void skin_backdrop_unload(int backdrop_id); #define BACKDROP_BUFFERNAME "#backdrop_buffer#" void skin_backdrop_set_buffer(int backdrop_id, struct skin_viewport *svp); -/* do the button loop as often as required for the peak meters to update - * with a good refresh rate. - * gwps is really gwps[NB_SCREENS]! don't wrap this in FOR_NB_SCREENS() - */ +/* Enter button loop updating peak meter at a high refresh rate */ int skin_wait_for_action(enum skinnable_screens skin, int context, int timeout); -void skin_load(enum skinnable_screens skin, enum screen_type screen, - const char *buf, bool isfile); struct gui_wps *skin_get_gwps(enum skinnable_screens skin, enum screen_type screen); void gui_sync_skin_init(void); diff --git a/apps/gui/skin_engine/skin_parser.c b/apps/gui/skin_engine/skin_parser.c index af0c83a8b2..939d1ca457 100644 --- a/apps/gui/skin_engine/skin_parser.c +++ b/apps/gui/skin_engine/skin_parser.c @@ -115,8 +115,9 @@ get_param(struct skin_element *element, int param_number) return ¶ms[param_number]; } -/* which screen are we parsing for? */ +/* which screen and skin are we parsing for? */ static enum screen_type curr_screen; +static enum skinnable_screens curr_skin; /* the current viewport */ static struct skin_element *curr_viewport_element; @@ -997,6 +998,10 @@ static int parse_progressbar_tag(struct skin_element* element, pb->nofill = false; pb->noborder = false; pb->nobar = false; + pb->hold = false; + pb->hold_size = 1; + pb->level = pb->hold_pos = 0; + pb->last_tick = pb->hold_tick = 0; pb->image = PTRTOSKINOFFSET(skin_buffer, NULL); pb->slider = PTRTOSKINOFFSET(skin_buffer, NULL); pb->backdrop = PTRTOSKINOFFSET(skin_buffer, NULL); @@ -1082,14 +1087,16 @@ static int parse_progressbar_tag(struct skin_element* element, { eINVERT = 0, eNOFILL, eNOBORDER, eNOBAR, eSLIDER, eIMAGE, eBACKDROP, eVERTICAL, eHORIZONTAL, eNOTOUCH, eSETTING, eSETTING_OFFSET, - e_PB_TAG_COUNT + eHOLD, e_PB_TAG_COUNT }; static const char *pb_options[e_PB_TAG_COUNT + 1] = {[eINVERT] = "invert", [eNOFILL] = "nofill", [eNOBORDER] = "noborder", [eNOBAR] = "nobar", [eSLIDER] = "slider", [eIMAGE] = "image", [eBACKDROP] = "backdrop", [eVERTICAL] = "vertical", [eHORIZONTAL] = "horizontal", - [eNOTOUCH] = "notouch", [eSETTING] = "setting", [eSETTING_OFFSET] = "soffset", [e_PB_TAG_COUNT] = NULL}; + [eNOTOUCH] = "notouch", [eSETTING] = "setting", + [eSETTING_OFFSET] = "soffset", [eHOLD] = "hold", + [e_PB_TAG_COUNT] = NULL}; int pb_op; while (curr_param < element->params_count) @@ -1107,6 +1114,32 @@ static int parse_progressbar_tag(struct skin_element* element, pb->noborder = true; else if (pb_op == eNOBAR) pb->nobar = true; + else if (pb_op == eHOLD) + { + pb->hold = true; + /* optional: the thickness of the held peak */ + if (curr_param+1 < element->params_count) + { + struct skin_tag_parameter *next = param + 1; + int size = -1; + if (next->type == INTEGER) + size = next->data.number; + else if (next->type == STRING) + { + text = SKINOFFSETTOPTR(skin_buffer, next->data.text); + if (isdigit(*text)) + size = atoi(text); + } + if (size >= 0) + { + if (size == 0) + return WPS_ERROR_INVALID_PARAM; + pb->hold_size = size; + curr_param++; + param++; + } + } + } else if (pb_op == eSLIDER) { if (curr_param+1 < element->params_count) @@ -2449,6 +2482,14 @@ static int skin_element_callback(struct skin_element* element, void* data) case SKIN_TOKEN_DRAW_INBUILTBAR: function = parse_statusbar_tags; break; +#if defined(HAVE_QUICKSCREEN) && !defined(__PCTOOL__) + case SKIN_TOKEN_TOP_QUICKSETTING_NAME: + /* Disable built-in Quickscreen UI if skin draws its own, + as suggested by the use of the %QT tag */ + if (curr_skin == CUSTOM_STATUSBAR) + quickscreen_set_skinned(curr_screen, true); + break; +#endif case SKIN_TOKEN_LIST_TITLE_TEXT: #ifndef __PCTOOL__ sb_skin_has_title(curr_screen); @@ -2591,10 +2632,11 @@ static int skin_element_callback(struct skin_element* element, void* data) return CALLBACK_OK; } -/* to setup up the wps-data from a format-buffer (isfile = false) - from a (wps-)file (isfile = true)*/ -bool skin_data_load(enum screen_type screen, struct wps_data *wps_data, - const char *buf, bool isfile, struct skin_stats *stats) +/* Set up skin data from a format buffer (isfile = false) + or from skin file (isfile = true) */ +bool skin_data_load(enum skinnable_screens skin, enum screen_type screen, + struct wps_data *wps_data, const char *buf, bool isfile, + struct skin_stats *stats) { char *wps_buffer = NULL; if (!wps_data || !buf) @@ -2624,6 +2666,7 @@ bool skin_data_load(enum screen_type screen, struct wps_data *wps_data, skin_data_reset(wps_data); wps_data->wps_loaded = false; curr_screen = screen; + curr_skin = skin; curr_line = NULL; curr_vp = NULL; curr_viewport_element = NULL; diff --git a/apps/gui/skin_engine/skin_tokens.c b/apps/gui/skin_engine/skin_tokens.c index 32daf5dc00..50faaa7ec1 100644 --- a/apps/gui/skin_engine/skin_tokens.c +++ b/apps/gui/skin_engine/skin_tokens.c @@ -290,7 +290,7 @@ void wps_playlist_percent_prepare(void) if (fd >= 0) { unsigned int afmt = probe_file_format(info.filename); - off_t size = filesize(fd); + off_t size = ffilesize(fd); if (size > 0) { @@ -1354,6 +1354,8 @@ const char *get_token_value(struct gui_wps *gwps, case SKIN_TOKEN_PLAYLIST_PERCENT: { int playlist_amt = playlist_amount(); + if (playlist_amt <= 0) + return NULL; int current_pos = playlist_get_display_index() + offset; int percentage = current_pos * 100 / playlist_amt; @@ -1839,16 +1841,24 @@ const char *get_token_value(struct gui_wps *gwps, numeric_ret = 12; break;) } - snprintf(buf, buf_size, "%lu.%1lu", samprk/1000,samprk%1000); + /* in kHz, without trailing zeros: 44.1, 22.05, 48 */ + snprintf(buf, buf_size, "%lu.%03lu", samprk / 1000, samprk % 1000); + { + char *end = buf + strlen(buf) - 1; + while (*end == '0') + *end-- = '\0'; + if (*end == '.') + *end = '\0'; + } numeric_buf = buf; goto gtv_ret_numeric_tag_info; } case SKIN_TOKEN_REC_ENCODER: { - int rec_format = global_settings.rec_format+1; /* WAV, AIFF, WV, MPEG */ + /* as a conditional 1 to 4: WAV, AIFF, WV, MPEG */ if (intval) - *intval = rec_format; - switch (rec_format) + *intval = global_settings.rec_format + 1; + switch (global_settings.rec_format) { case REC_FORMAT_PCM_WAV: return "wav"; @@ -1857,74 +1867,28 @@ const char *get_token_value(struct gui_wps *gwps, case REC_FORMAT_WAVPACK: return "wv"; case REC_FORMAT_MPA_L3: - return "MP3"; + return "mp3"; default: return NULL; } break; } case SKIN_TOKEN_REC_BITRATE: - if (global_settings.rec_format == REC_FORMAT_MPA_L3) + /* MP3 only: the bitrate in kbps; as a conditional, the index of + * the setting, 1 for 8 kbps up to 18 for 320 kbps */ + if (global_settings.rec_format == REC_FORMAT_MPA_L3 && + global_settings.mp3_enc_config.bitrate < MP3_ENC_NUM_BITR) { - #if 0 /* FIXME: I dont know if this is needed? */ - switch (1<= (int)ARRAYLEN(trig_name)) + return NULL; + numeric_ret = trig + 1; + numeric_buf = (char *)trig_name[trig]; + goto gtv_ret_numeric_tag_info; + } + + /* recording warnings, in hex; nothing while there are none */ + case SKIN_TOKEN_REC_WARNING: + if (!(audio_status() & AUDIO_STATUS_WARNING)) + return NULL; + snprintf(buf, buf_size, "%08lX", + (unsigned long)pcm_rec_get_warnings()); + return buf; + + /* input source; as a conditional, the same on every target: mic, + * line in, digital, FM radio */ + case SKIN_TOKEN_REC_SOURCE: + switch (global_settings.rec_source) + { +#ifdef HAVE_MIC_REC + case AUDIO_SRC_MIC: + numeric_ret = 1; + numeric_buf = (char *)str(LANG_RECORDING_SRC_MIC); + break; +#endif +#ifdef HAVE_LINE_REC + case AUDIO_SRC_LINEIN: + numeric_ret = 2; + numeric_buf = (char *)str(LANG_LINE_IN); + break; +#endif +#ifdef HAVE_SPDIF_REC + case AUDIO_SRC_SPDIF: + numeric_ret = 3; + numeric_buf = (char *)str(LANG_RECORDING_SRC_DIGITAL); + break; +#endif +#ifdef HAVE_FMRADIO_REC + case AUDIO_SRC_FMRADIO: + numeric_ret = 4; + numeric_buf = (char *)str(LANG_FM_RADIO); + break; +#endif + default: + return NULL; + } + goto gtv_ret_numeric_tag_info; + + /* gain of the current source in dB - the left channel's for line + * in and FM radio; nothing for a source without one */ + case SKIN_TOKEN_REC_GAIN: + switch (global_settings.rec_source) + { +#ifdef HAVE_MIC_REC + case AUDIO_SRC_MIC: + format_sound_value_ex(buf, buf_size, SOUND_MIC_GAIN, + global_settings.rec_mic_gain, true); + return buf; +#endif +#if defined(HAVE_LINE_REC) || defined(HAVE_FMRADIO_REC) + HAVE_LINE_REC_(case AUDIO_SRC_LINEIN:) + HAVE_FMRADIO_REC_(case AUDIO_SRC_FMRADIO:) + format_sound_value_ex(buf, buf_size, SOUND_LEFT_GAIN, + global_settings.rec_left_gain, true); + return buf; +#endif + default: + return NULL; + } + #endif /* HAVE_RECORDING */ case SKIN_TOKEN_CURRENT_SCREEN: diff --git a/apps/gui/skin_engine/wps_internals.h b/apps/gui/skin_engine/wps_internals.h index e39358b80d..99cf6463b3 100644 --- a/apps/gui/skin_engine/wps_internals.h +++ b/apps/gui/skin_engine/wps_internals.h @@ -25,6 +25,7 @@ #define _WPS_ENGINE_INTERNALS_ #include "tag_table.h" +#include "skin_engine.h" #include "skin_parser.h" #include "gesture.h" #ifndef __PCTOOL__ @@ -44,12 +45,11 @@ struct skin_stats *skin_get_stats(int number, int screen); #define skin_clear_stats(stats) memset(stats, 0, sizeof(struct skin_stats)) bool skin_backdrop_get_debug(int index, char **path, int *ref_count, size_t *size); -/* - * setup up the skin-data from a format-buffer (isfile = false) - * or from a skinfile (isfile = true) - */ -bool skin_data_load(enum screen_type screen, struct wps_data *wps_data, - const char *buf, bool isfile, struct skin_stats *stats); +/* Set up skin data from a format buffer (isfile = false) + or from skin file (isfile = true) */ +bool skin_data_load(enum skinnable_screens skin, enum screen_type screen, + struct wps_data *wps_data, const char *buf, bool isfile, + struct skin_stats *stats); /* Timeout unit expressed in HZ. In WPS, all timeouts are given in seconds (possibly with a decimal fraction) but stored as integer values. @@ -136,6 +136,12 @@ struct progressbar { bool nofill; bool noborder; bool nobar; + /* peak meter bars: fall back at the peak release rate and mark the + * held peak, as the recording screen's peak meter does */ + bool hold; + int hold_size; /* thickness of the held peak, in pixels */ + int level, hold_pos; /* in units of MAX_PEAK */ + long last_tick, hold_tick; OFFSETTYPE(struct gui_img *) slider; OFFSETTYPE(struct gui_img *) backdrop; diff --git a/apps/gui/splash.c b/apps/gui/splash.c index 8619d36265..440164984b 100644 --- a/apps/gui/splash.c +++ b/apps/gui/splash.c @@ -305,8 +305,18 @@ static bool splash_internal(struct screen * screen, const char *fmt, va_list ap, fg = screen->get_foreground(); bg = screen->get_background(); - broken = (fg == bg) || - (bg == 63422 && fg == 65535); /* -> iPod reFresh themes from '22 */ + if (fg == bg) + broken = true; + +#if defined(HAVE_LCD_COLOR) + /* iPod reFresh themes from '22 */ + if (screen->is_color) + { + if (bg == SCREEN_COLOR_TO_NATIVE(screen, LCD_RGBPACK(244, 244, 244)) && + fg == SCREEN_COLOR_TO_NATIVE(screen, LCD_RGBPACK(255, 255, 255))) + broken = true; + } +#endif vp->drawmode = DRMODE_FG; /* can't do vp->fg_pattern here, since set_foreground does a bit more on diff --git a/apps/gui/statusbar-skinned.c b/apps/gui/statusbar-skinned.c index 6fcb90ece5..27d3a924f2 100644 --- a/apps/gui/statusbar-skinned.c +++ b/apps/gui/statusbar-skinned.c @@ -101,6 +101,9 @@ void sb_process(enum screen_type screen, struct wps_data *data, bool preprocess) { sbs_loaded[screen] = false; sbs_has_title[screen] = false; +#ifdef HAVE_QUICKSCREEN + quickscreen_set_skinned(screen, false); /* Revert to built-in QS UI */ +#endif viewportmanager_theme_enable(screen, false, NULL); return; } diff --git a/apps/gui/wps.c b/apps/gui/wps.c index add47d08db..12fda68824 100644 --- a/apps/gui/wps.c +++ b/apps/gui/wps.c @@ -170,6 +170,26 @@ static int skintouch_to_wps(void) { int offset = 0; struct gui_wps *gwps = skin_get_gwps(WPS, SCREEN_MAIN); + + struct gesture_event gevent; + if (global_settings.touch_enable_flick_shortcuts && + action_gesture_get_event(&gevent)) + { + switch (gesture_flick_get(&gevent)) + { + case GESTURE_FLICK_TOP: + return ACTION_WPS_QUICKSCREEN; + case GESTURE_FLICK_LEFT: + return ACTION_WPS_BROWSE; + case GESTURE_FLICK_RIGHT: + return ACTION_WPS_VIEW_PLAYLIST; + case GESTURE_FLICK_BOTTOM: + return ACTION_WPS_CONTEXT; + default: + break; + } + } + int button = skin_get_touchaction(gwps, &offset); switch (button) { @@ -696,7 +716,7 @@ static inline int action_wpsab_single(long button) long gui_wps_show(void) { /* NOTE: if USBAudio ever gets its own DSP channel, this block can go away! */ -#ifdef USB_ENABLE_AUDIO +#if defined(USB_ENABLE_AUDIO) || defined(HAVE_HOST_USB_AUDIO) if (usb_audio_get_active()) { splash(HZ*2, ID2P(LANG_USB_DAC_ACTIVE)); @@ -982,7 +1002,7 @@ long gui_wps_show(void) bool enter_shortcuts_menu = global_settings.shortcuts_replaces_qs; if (!enter_shortcuts_menu) { - int ret = quick_screen_quick(button); + int ret = quickscreen_show(button); if (ret == QUICKSCREEN_IN_USB) return GO_TO_ROOT; else if (ret == QUICKSCREEN_GOTO_SHORTCUTS_MENU) diff --git a/apps/iap/iap-core.c b/apps/iap/iap-core.c index 0b4ad13477..6627259e36 100644 --- a/apps/iap/iap-core.c +++ b/apps/iap/iap-core.c @@ -96,7 +96,7 @@ int iap_repeatbtn = 0; unsigned int iap_timeoutbtn = 0; bool iap_btnrepeat = false, iap_btnshuffle = false; -static long thread_stack[(DEFAULT_STACK_SIZE*6)/sizeof(long)]; +static long thread_stack[(DEFAULT_STACK_SIZE*8)/sizeof(long)]; static struct event_queue iap_queue; /* These are pointer used to manage a dynamically allocated buffer which diff --git a/apps/iap/iap-core.h b/apps/iap/iap-core.h index 38a893374a..3d42dc0f89 100644 --- a/apps/iap/iap-core.h +++ b/apps/iap/iap-core.h @@ -25,11 +25,8 @@ #include "playlist.h" #include "iap.h" -#define LOGF_ENABLE -/* #undef LOGF_ENABLE */ -#ifdef LOGF_ENABLE - #include "logf.h" -#endif +//#define LOGF_ENABLE +#include "logf.h" /* The Model ID of the iPod we emulate. Currently a 160GB classic */ #define IAP_IPOD_MODEL (0x00130200U) diff --git a/apps/iap/iap-lingo4.c b/apps/iap/iap-lingo4.c index 746d59f397..d29f4d1612 100644 --- a/apps/iap/iap-lingo4.c +++ b/apps/iap/iap-lingo4.c @@ -16,6 +16,7 @@ * ******************************************************************************/ +#include "misc.h" #include "iap-core.h" #include "iap-lingo.h" #include "dir.h" @@ -1034,12 +1035,18 @@ void iap_handlepkt_mode4(const unsigned int len, const unsigned char *buf) * 5 0x14 Command ID (bits 7:0) * 6 0xE5 Telegram payload checksum byte * - * We return ROCKBOX, should this be definable? * Should it be Volume Name? */ { + char ipod_name[32] = "Rockbox"; + int fd = open_utf8(ROCKBOX_DIR "/playername.txt", O_RDONLY); + if (fd >= 0) { + read_line(fd, ipod_name, sizeof(ipod_name)); + close(fd); + ipod_name[sizeof(ipod_name)-1] = 0; + } IAP_TX_INIT4(0x04, 0x0015); - IAP_TX_PUT_STRING("ROCKBOX"); + IAP_TX_PUT_STRING(ipod_name); iap_send_tx(); break; } diff --git a/apps/keymaps/keymap-ctru.c b/apps/keymaps/keymap-3ds.c similarity index 99% rename from apps/keymaps/keymap-ctru.c rename to apps/keymaps/keymap-3ds.c index 76f5e132d6..ab4f4c66c8 100644 --- a/apps/keymaps/keymap-ctru.c +++ b/apps/keymaps/keymap-3ds.c @@ -18,7 +18,7 @@ * ****************************************************************************/ -/* Button Code Definitions for Civic Type R (ctru) target */ +/* Button Code Definitions for the Nintendo 3DS target */ #include #include diff --git a/apps/keymaps/keymap-dx50.c b/apps/keymaps/keymap-dx50.c index c6bb814469..dd5a0a2e99 100644 --- a/apps/keymaps/keymap-dx50.c +++ b/apps/keymaps/keymap-dx50.c @@ -228,6 +228,7 @@ const struct button_mapping* target_get_context_mapping(int context) case CONTEXT_BOOKMARKSCREEN: { return button_context_bmark; } case CONTEXT_QUICKSCREEN: { return button_context_quickscreen; } case CONTEXT_PITCHSCREEN: { return button_context_pitchscreen; } + case CONTEXT_YESNOSCREEN: { return button_context_yesno; } case CONTEXT_CUSTOM | CONTEXT_SETTINGS: case CONTEXT_SETTINGS_RECTRIGGER: { return button_context_settings_right_is_inc; } case CONTEXT_TREE: diff --git a/apps/keymaps/keymap-hidizsap80max.c b/apps/keymaps/keymap-hidizsap80max.c new file mode 100644 index 0000000000..ca23b3b050 --- /dev/null +++ b/apps/keymaps/keymap-hidizsap80max.c @@ -0,0 +1,246 @@ +/*************************************************************************** + * __________ __ ___ + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2025 Solomon Peachy + * Copyright (C) 2026 by Jerry Shen + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#include +#include +#include + +#include "config.h" +#include "action.h" +#include "button.h" +#include "settings.h" + +static const struct button_mapping button_context_standard[] = +{ + { ACTION_STD_KEYLOCK, BUTTON_POWER|BUTTON_PLAY, BUTTON_POWER }, + + { ACTION_STD_PREV, BUTTON_SCROLL_BACK, BUTTON_NONE }, + { ACTION_STD_NEXT, BUTTON_SCROLL_FWD, BUTTON_NONE }, + + { ACTION_STD_OK, BUTTON_POWER|BUTTON_REL, BUTTON_POWER }, + { ACTION_STD_OK, BUTTON_NEXT|BUTTON_REL, BUTTON_NEXT }, + { ACTION_STD_CANCEL, BUTTON_PREV|BUTTON_REL, BUTTON_PREV }, + + { ACTION_STD_OK, BUTTON_PLAY|BUTTON_REL, BUTTON_PLAY }, + { ACTION_STD_CONTEXT, BUTTON_PLAY|BUTTON_REPEAT, BUTTON_PLAY }, + + LAST_ITEM_IN_LIST +}; + + +static const struct button_mapping button_context_wps[] = +{ + { ACTION_WPS_MENU, BUTTON_POWER|BUTTON_REL, BUTTON_POWER }, + { ACTION_WPS_PLAY, BUTTON_PLAY|BUTTON_REL, BUTTON_PLAY }, + { ACTION_WPS_STOP, BUTTON_PLAY|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_WPS_VOLDOWN, BUTTON_SCROLL_FWD, BUTTON_NONE }, + { ACTION_WPS_VOLUP, BUTTON_SCROLL_BACK, BUTTON_NONE }, + { ACTION_WPS_SKIPNEXT, BUTTON_NEXT|BUTTON_REL, BUTTON_NEXT }, + { ACTION_WPS_SKIPPREV, BUTTON_PREV|BUTTON_REL, BUTTON_PREV }, + { ACTION_WPS_SEEKFWD, BUTTON_NEXT|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_WPS_STOPSEEK, BUTTON_NEXT|BUTTON_REL, BUTTON_NEXT|BUTTON_REPEAT }, + { ACTION_WPS_SEEKBACK, BUTTON_PREV|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_WPS_STOPSEEK, BUTTON_PREV|BUTTON_REL, BUTTON_PREV|BUTTON_REPEAT }, + { ACTION_STD_KEYLOCK, BUTTON_POWER|BUTTON_PLAY, BUTTON_POWER }, + + LAST_ITEM_IN_LIST +}; + + +static const struct button_mapping button_context_list[] = +{ + LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD), +}; + + +static const struct button_mapping button_context_tree[] = +{ + LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_LIST), +}; + + +static const struct button_mapping button_context_listtree_scroll_with_combo[] = +{ + LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_CUSTOM | CONTEXT_TREE), +}; + + +static const struct button_mapping button_context_listtree_scroll_without_combo[] = +{ + LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_CUSTOM | CONTEXT_TREE), +}; + + +static const struct button_mapping button_context_settings[] = +{ + { ACTION_SETTINGS_DEC, BUTTON_SCROLL_FWD, BUTTON_NONE }, + { ACTION_SETTINGS_INC, BUTTON_SCROLL_BACK, BUTTON_NONE }, + { ACTION_SETTINGS_INC, BUTTON_NEXT, BUTTON_NONE }, + { ACTION_SETTINGS_DEC, BUTTON_PREV, BUTTON_NONE }, + + LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD), +}; + + +static const struct button_mapping button_context_settings_right_is_inc[] = +{ + LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD), +}; + + +static const struct button_mapping button_context_mainmenu[] = +{ + LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_TREE), +}; + + +static const struct button_mapping button_context_yesno[] = +{ + { ACTION_YESNO_ACCEPT, BUTTON_PLAY|BUTTON_REL, BUTTON_PLAY }, + { ACTION_YESNO_ACCEPT, BUTTON_POWER|BUTTON_REL, BUTTON_POWER }, + { ACTION_STD_CANCEL, BUTTON_PREV|BUTTON_REL, BUTTON_PREV }, + + LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD), +}; + + +static const struct button_mapping button_context_colorchooser[] = +{ + LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_SETTINGS), +}; + + +static const struct button_mapping button_context_eq[] = +{ + { ACTION_SETTINGS_INCBIGSTEP, BUTTON_PLAY|BUTTON_SCROLL_BACK, BUTTON_NONE }, + { ACTION_SETTINGS_DECBIGSTEP, BUTTON_PLAY|BUTTON_SCROLL_FWD, BUTTON_NONE }, + + { ACTION_SETTINGS_INC, BUTTON_SCROLL_BACK, BUTTON_NONE }, + { ACTION_SETTINGS_DEC, BUTTON_SCROLL_FWD, BUTTON_NONE }, + + { ACTION_NONE, BUTTON_PREV, BUTTON_NONE }, + { ACTION_NONE, BUTTON_NEXT, BUTTON_NONE }, + { ACTION_STD_PREV, BUTTON_PREV|BUTTON_REL, BUTTON_PREV }, + { ACTION_STD_PREVREPEAT, BUTTON_PREV|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_STD_NEXT, BUTTON_NEXT|BUTTON_REL, BUTTON_NEXT }, + { ACTION_STD_NEXTREPEAT, BUTTON_NEXT|BUTTON_REPEAT, BUTTON_NONE }, + + { ACTION_STD_OK, BUTTON_PLAY|BUTTON_REL, BUTTON_PLAY }, + { ACTION_STD_CANCEL, BUTTON_POWER|BUTTON_REL, BUTTON_POWER }, + + LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_SETTINGS), +}; + + +static const struct button_mapping button_context_keyboard[] = +{ + { ACTION_KBD_LEFT, BUTTON_SCROLL_BACK, BUTTON_NONE }, + { ACTION_KBD_RIGHT, BUTTON_SCROLL_FWD, BUTTON_NONE }, + { ACTION_KBD_CURSOR_LEFT, BUTTON_PLAY | BUTTON_SCROLL_BACK, BUTTON_NONE }, + { ACTION_KBD_CURSOR_RIGHT, BUTTON_PLAY | BUTTON_SCROLL_FWD, BUTTON_NONE }, + { ACTION_KBD_DONE, BUTTON_PLAY | BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_KBD_SELECT, BUTTON_PLAY | BUTTON_REL, BUTTON_NONE }, + { ACTION_KBD_ABORT, BUTTON_POWER, BUTTON_NONE }, + { ACTION_KBD_UP, BUTTON_NEXT, BUTTON_NONE }, + { ACTION_KBD_UP, BUTTON_NEXT | BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_KBD_DOWN, BUTTON_PREV, BUTTON_NONE }, + { ACTION_KBD_DOWN, BUTTON_PREV | BUTTON_REPEAT, BUTTON_NONE }, + + LAST_ITEM_IN_LIST +}; + + +static const struct button_mapping button_context_bmark[] = +{ + LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_LIST), +}; + + +static const struct button_mapping button_context_time[] = +{ + LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_SETTINGS), +}; + + +static const struct button_mapping button_context_quickscreen[] = +{ + { ACTION_QS_TOP, BUTTON_SCROLL_BACK, BUTTON_NONE }, + { ACTION_QS_DOWN, BUTTON_SCROLL_FWD, BUTTON_NONE }, + { ACTION_QS_LEFT, BUTTON_PREV|BUTTON_REL, BUTTON_NONE }, + { ACTION_QS_LEFT, BUTTON_PREV|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_QS_RIGHT, BUTTON_NEXT|BUTTON_REL, BUTTON_NONE }, + { ACTION_QS_RIGHT, BUTTON_NEXT|BUTTON_REPEAT, BUTTON_NONE }, + + LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD) +}; + + +static const struct button_mapping button_context_pitchscreen[] = +{ + { ACTION_PS_DEC_SMALL, BUTTON_SCROLL_FWD, BUTTON_NONE }, + { ACTION_PS_INC_SMALL, BUTTON_SCROLL_BACK, BUTTON_NONE }, + { ACTION_PS_NUDGE_RIGHT, BUTTON_NEXT, BUTTON_NONE }, + { ACTION_PS_NUDGE_LEFT, BUTTON_PREV, BUTTON_NONE }, + { ACTION_PS_EXIT, BUTTON_POWER, BUTTON_NONE }, + + LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD) +}; + + +static const struct button_mapping button_context_radio[] = +{ + LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_SETTINGS) +}; + + +const struct button_mapping* target_get_context_mapping(int context) +{ + switch (context & ~CONTEXT_LOCKED) + { + case CONTEXT_STD: { return button_context_standard; } + case CONTEXT_WPS: { return button_context_wps; } + case CONTEXT_LIST: { return button_context_list; } + case CONTEXT_MAINMENU: { return button_context_mainmenu; } + case CONTEXT_CUSTOM | CONTEXT_TREE: { return button_context_tree; } + case CONTEXT_SETTINGS: { return button_context_settings; } + case CONTEXT_SETTINGS_COLOURCHOOSER: { return button_context_colorchooser; } + case CONTEXT_SETTINGS_EQ: { return button_context_eq; } + case CONTEXT_SETTINGS_TIME: { return button_context_time; } + case CONTEXT_YESNOSCREEN: { return button_context_yesno; } + case CONTEXT_KEYBOARD: { return button_context_keyboard; } + case CONTEXT_FM: { return button_context_radio; } + case CONTEXT_BOOKMARKSCREEN: { return button_context_bmark; } + case CONTEXT_QUICKSCREEN: { return button_context_quickscreen; } + case CONTEXT_PITCHSCREEN: { return button_context_pitchscreen; } + case CONTEXT_CUSTOM | CONTEXT_SETTINGS: + case CONTEXT_SETTINGS_RECTRIGGER: { return button_context_settings_right_is_inc; } + case CONTEXT_TREE: + { + if(global_settings.hold_lr_for_scroll_in_list) + { + return button_context_listtree_scroll_without_combo; + } + return button_context_listtree_scroll_with_combo; + } + } + + return button_context_standard; +} diff --git a/apps/keymaps/keymap-ypr0.c b/apps/keymaps/keymap-ypr0.c index 099bc5a338..e62ff652fd 100644 --- a/apps/keymaps/keymap-ypr0.c +++ b/apps/keymaps/keymap-ypr0.c @@ -239,6 +239,9 @@ static const struct button_mapping button_context_radio[] = { { ACTION_FM_PLAY, BUTTON_SELECT | BUTTON_REL, BUTTON_SELECT }, { ACTION_FM_NEXT_PRESET, BUTTON_USER | BUTTON_RIGHT, BUTTON_NONE }, { ACTION_FM_PREV_PRESET, BUTTON_USER | BUTTON_LEFT, BUTTON_NONE }, +#ifdef HAVE_RECORDING + { ACTION_FM_RECORD, BUTTON_USER | BUTTON_REL, BUTTON_USER }, +#endif /* Volume */ { ACTION_SETTINGS_INC, BUTTON_UP | BUTTON_REPEAT, BUTTON_NONE }, @@ -252,6 +255,22 @@ static const struct button_mapping button_context_radio[] = { { ACTION_STD_NEXTREPEAT, BUTTON_RIGHT | BUTTON_REPEAT, BUTTON_NONE }, }; /* button_context_radio */ +#ifdef HAVE_RECORDING +/* User - the Rec key on the YP-CP3 - starts and pauses, held opens a new + * file; left/right set the gain of the selected line */ +static const struct button_mapping button_context_recscreen[] = { + { ACTION_REC_PAUSE, BUTTON_USER|BUTTON_REL, BUTTON_USER }, + { ACTION_REC_NEWFILE, BUTTON_USER|BUTTON_REPEAT, BUTTON_USER }, + { ACTION_STD_MENU, BUTTON_MENU|BUTTON_REL, BUTTON_MENU }, + { ACTION_SETTINGS_INC, BUTTON_RIGHT, BUTTON_NONE }, + { ACTION_SETTINGS_INCREPEAT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_SETTINGS_DEC, BUTTON_LEFT, BUTTON_NONE }, + { ACTION_SETTINGS_DECREPEAT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE }, + + LAST_ITEM_IN_LIST__NEXTLIST(CONTEXT_STD) +}; /* button_context_recscreen */ +#endif + const struct button_mapping* get_context_mapping(int context) { switch (context & ~CONTEXT_LOCKED) @@ -296,6 +315,10 @@ const struct button_mapping* get_context_mapping(int context) return button_context_pitchscreen; case CONTEXT_KEYBOARD: return button_context_keyboard; +#ifdef HAVE_RECORDING + case CONTEXT_RECSCREEN: + return button_context_recscreen; +#endif } return button_context_standard; } diff --git a/apps/lang/bulgarian.lang b/apps/lang/bulgarian.lang index f0d165cceb..ccb7fafdd8 100644 --- a/apps/lang/bulgarian.lang +++ b/apps/lang/bulgarian.lang @@ -1009,10 +1009,10 @@ desc: in the equalizer settings menu user: core - *: "Peak Filter %d" + *: "Peak Filter" - *: "Пиков филтър %d" + *: "Пиков филтър" *: "Пиков филтър" @@ -9104,15 +9104,15 @@ user: core *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Bass Cutoff" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Bass Cutoff" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Отрязване на баса" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Отрязване на баса" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Отрязване на баса" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Отрязване на баса" @@ -9121,15 +9121,15 @@ user: core *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Treble Cutoff" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Treble Cutoff" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Отрязване на високите" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Отрязване на високите" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Отрязване на високите" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Отрязване на високите" @@ -17102,128 +17102,64 @@ - id: LANG_SET_CONTEXT_ITEM - desc: WPS context item + id: LANG_EQUALIZER_BAND + desc: in the equalizer settings menu user: core - *: "Set Context Item" + *: "Filter %d" - *: "Избери елемент за контекст" + *: "Филтър %d" - *: "Избери елемент за контекст" + *: "Филтър" - id: LANG_FILE_NOT_FOUND - desc: When file does not exist + id: LANG_EQUALIZER_FILTER_TYPE + desc: in the equalizer settings menu user: core - *: "File not found" + *: "Filter Type" - *: "Не е намерен файл" + *: "Вид на филтъра" - *: "Не е намерен файл" + *: "Вид на филтъра" - id: LANG_SHOW_IN_FILES - desc: Reveal item in File Browser + id: LANG_COMPOSITE_VIDEO_OUTPUT + desc: Enables composite video framebuffer mirroring on supported targets user: core - *: "Show in Files" + *: none + videoout: "Composite Video Output" - *: "Покажи във файлов мениджър" + *: none + videoout: "Композитен видео изход" - *: "Покажи във файлов мениджър" + *: none + videoout: "Композитен видео изход" - id: LANG_CHANNEL_SWAP - desc: in sound_settings + id: LANG_TOUCHSCREEN_FLICK_SHORTCUTS + desc: in settings menu user: core - *: "Swap Left & Right" + *: none + touchscreen: "Touchscreen Flick Shortcuts" - *: "Размяна на ляво/дясно" + *: none + touchscreen: "Преки пътища на тъчскрийн" - *: "Размяна на ляво и дясно" - - - - id: LANG_COUNTDOWN_TIMER_SET - desc: countdown_timer plugin - header shown on the setup screen where the user enters the countdown duration - user: core - - *: "SET TIMER" - - - *: "НАСТРОЙКА ТАЙМЕР" - - - *: "Настройка таймер" - - - - id: LANG_COUNTDOWN_TIMER_RUNNING - desc: countdown_timer plugin - status label shown while the countdown is active - user: core - - *: "RUNNING" - - - *: "АКТИВЕН" - - - *: "Активен" - - - - id: LANG_COUNTDOWN_TIMER_PAUSED - desc: countdown_timer plugin - status label shown while the countdown is paused - user: core - - *: "PAUSED" - - - *: "ПАУЗА" - - - *: "Пауза" - - - - id: LANG_COUNTDOWN_TIMER_OVERTIME - desc: countdown_timer plugin - status label shown when the countdown has passed zero and is counting up - user: core - - *: "OVERTIME" - - - *: "ИЗВЪНРЕДНО" - - - *: "Извънредно" - - - - id: LANG_COUNTDOWN_TIMER_FINISHED - desc: countdown_timer plugin - status label shown at the moment the countdown expires - user: core - - *: "FINISHED" - - - *: "ЗАВЪРШЕН" - - - *: "Завършен" + *: none + touchscreen: "Преки пътища на тъчскрийн" diff --git a/apps/lang/chinese-simp.lang b/apps/lang/chinese-simp.lang index 85437f0223..19273d4842 100644 --- a/apps/lang/chinese-simp.lang +++ b/apps/lang/chinese-simp.lang @@ -764,10 +764,10 @@ desc: in the equalizer settings menu user: core - *: "Peak Filter %d" + *: "Peak Filter" - *: "带通滤波器%d" + *: "带通滤波器" *: "带通滤波器" @@ -8888,15 +8888,15 @@ user: core *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Bass Cutoff" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Bass Cutoff" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "低频截止频率" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "低频截止频率" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "低频截止频率" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "低频截止频率" @@ -9094,15 +9094,15 @@ user: core *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Treble Cutoff" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Treble Cutoff" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "高频截止频率" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "高频截止频率" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "高频截止频率" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "高频截止频率" @@ -17106,3 +17106,65 @@ *: "设置上下文项目" + + id: LANG_EQUALIZER_BAND + desc: in the equalizer settings menu + user: core + + *: "Filter %d" + + + *: "滤波器 %d" + + + *: "滤波器" + + + + id: LANG_EQUALIZER_FILTER_TYPE + desc: in the equalizer settings menu + user: core + + *: "Filter Type" + + + *: "滤波器类型" + + + *: "滤波器类型" + + + + id: LANG_COMPOSITE_VIDEO_OUTPUT + desc: Enables composite video framebuffer mirroring on supported targets + user: core + + *: none + videoout: "Composite Video Output" + + + *: none + videoout: "复合视频(AV)输出" + + + *: none + videoout: "复合视频输出" + + + + id: LANG_TOUCHSCREEN_FLICK_SHORTCUTS + desc: in settings menu + user: core + + *: none + touchscreen: "Touchscreen Flick Shortcuts" + + + *: none + touchscreen: "触控滑动快捷方式" + + + *: none + touchscreen: "触控滑动快捷方式" + + diff --git a/apps/lang/deutsch.lang b/apps/lang/deutsch.lang index 5ba440c881..4ded6c623f 100644 --- a/apps/lang/deutsch.lang +++ b/apps/lang/deutsch.lang @@ -1010,13 +1010,13 @@ desc: in the equalizer settings menu user: core - *: "Peak Filter %d" + *: "Peak Filter" - *: "Bandfilter %d" + *: "Glockenfilter" - *: "Bandfilter" + *: "Glockenfilter" @@ -9092,15 +9092,15 @@ user: core *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Bass Cutoff" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Bass Cutoff" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Tiefengrenzfrequenz" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Tiefengrenzfrequenz" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Tiefengrenzfrequenz" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Tiefengrenzfrequenz" @@ -9109,15 +9109,15 @@ user: core *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Treble Cutoff" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Treble Cutoff" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Höhengrenzfrequenz" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Höhengrenzfrequenz" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Höhengrenzfrequenz" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Höhengrenzfrequenz" @@ -17118,3 +17118,65 @@ *: "Kontextelement festlegen" + + id: LANG_EQUALIZER_BAND + desc: in the equalizer settings menu + user: core + + *: "Filter %d" + + + *: "~Filter %d" + + + *: "~Filter" + + + + id: LANG_EQUALIZER_FILTER_TYPE + desc: in the equalizer settings menu + user: core + + *: "Filter Type" + + + *: "Filter Typ" + + + *: "Filter Typ" + + + + id: LANG_COMPOSITE_VIDEO_OUTPUT + desc: Enables composite video framebuffer mirroring on supported targets + user: core + + *: none + videoout: "Composite Video Output" + + + *: none + videoout: "Composite Video Ausgang" + + + *: none + videoout: "Composite Video Ausgang" + + + + id: LANG_TOUCHSCREEN_FLICK_SHORTCUTS + desc: in settings menu + user: core + + *: none + touchscreen: "Touchscreen Flick Shortcuts" + + + *: none + touchscreen: "Wischgesten Kurzbefehle" + + + *: none + touchscreen: "Wischgesten Kurzbefehle" + + diff --git a/apps/lang/english-us.lang b/apps/lang/english-us.lang index f2ca369671..83cace36fd 100644 --- a/apps/lang/english-us.lang +++ b/apps/lang/english-us.lang @@ -1004,10 +1004,10 @@ desc: in the equalizer settings menu user: core - *: "Peak Filter %d" + *: "Peak Filter" - *: "Peak Filter %d" + *: "Peak Filter" *: "Peak filter" @@ -9113,15 +9113,15 @@ user: core *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Bass Cutoff" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Bass Cutoff" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Bass Cutoff" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Bass Cutoff" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Bass Cutoff" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Bass Cutoff" @@ -9130,15 +9130,15 @@ user: core *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Treble Cutoff" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Treble Cutoff" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Treble Cutoff" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Treble Cutoff" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Treble Cutoff" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Treble Cutoff" @@ -17096,3 +17096,65 @@ *: "Set Context Item" + + id: LANG_EQUALIZER_BAND + desc: in the equalizer settings menu + user: core + + *: "Filter %d" + + + *: "Filter %d" + + + *: "Filter" + + + + id: LANG_EQUALIZER_FILTER_TYPE + desc: in the equalizer settings menu + user: core + + *: "Filter Type" + + + *: "Filter Type" + + + *: "Filter Type" + + + + id: LANG_COMPOSITE_VIDEO_OUTPUT + desc: Enables composite video framebuffer mirroring on supported targets + user: core + + *: none + videoout: "Composite Video Output" + + + *: none + videoout: "Composite Video Output" + + + *: none + videoout: "Composite Video Output" + + + + id: LANG_TOUCHSCREEN_FLICK_SHORTCUTS + desc: in settings menu + user: core + + *: none + touchscreen: "Touchscreen Flick Shortcuts" + + + *: none + touchscreen: "Touchscreen Flick Shortcuts" + + + *: none + touchscreen: "Touchscreen Flick Shortcuts" + + diff --git a/apps/lang/english.lang b/apps/lang/english.lang index 6ed795f602..112c4d9257 100644 --- a/apps/lang/english.lang +++ b/apps/lang/english.lang @@ -1091,10 +1091,10 @@ desc: in the equalizer settings menu user: core - *: "Peak Filter %d" + *: "Peak Filter" - *: "Peak Filter %d" + *: "Peak Filter" *: "Peak filter" @@ -9284,15 +9284,15 @@ user: core *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Bass Cutoff" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Bass Cutoff" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Bass Cutoff" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Bass Cutoff" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Bass Cutoff" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Bass Cutoff" @@ -9301,15 +9301,15 @@ user: core *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Treble Cutoff" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Treble Cutoff" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Treble Cutoff" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Treble Cutoff" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Treble Cutoff" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Treble Cutoff" @@ -17169,3 +17169,65 @@ *: "Finished" + + id: LANG_EQUALIZER_BAND + desc: in the equalizer settings menu + user: core + + *: "Filter %d" + + + *: "Filter %d" + + + *: "Filter" + + + + id: LANG_EQUALIZER_FILTER_TYPE + desc: in the equalizer settings menu + user: core + + *: "Filter Type" + + + *: "Filter Type" + + + *: "Filter Type" + + + + id: LANG_COMPOSITE_VIDEO_OUTPUT + desc: Enables composite video framebuffer mirroring on supported targets + user: core + + *: none + videoout: "Composite Video Output" + + + *: none + videoout: "Composite Video Output" + + + *: none + videoout: "Composite Video Output" + + + + id: LANG_TOUCHSCREEN_FLICK_SHORTCUTS + desc: in settings menu + user: core + + *: none + touchscreen: "Touchscreen Flick Shortcuts" + + + *: none + touchscreen: "Touchscreen Flick Shortcuts" + + + *: none + touchscreen: "Touchscreen Flick Shortcuts" + + diff --git a/apps/lang/espanol.lang b/apps/lang/espanol.lang index 6dffa08c33..b2309015ac 100644 --- a/apps/lang/espanol.lang +++ b/apps/lang/espanol.lang @@ -6925,13 +6925,13 @@ desc: in the equalizer settings menu user: core - *: "Peak Filter %d" + *: "Peak Filter" - *: "Filtro de pico %d" + *: "Filtro de Pico" - *: "Filtro de pico" + *: "Filtro de Pico" @@ -7392,15 +7392,15 @@ user: core *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Bass Cutoff" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Bass Cutoff" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Frecuencia de corte de bajos" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Corte de Graves" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Frecuencia de corte de bajos" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Corte de Graves" @@ -8934,15 +8934,15 @@ user: core *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Treble Cutoff" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Treble Cutoff" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Frec. de corte de agudos" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Corte de Agudos" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Frecuencia de corte de agudos" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Corte de Agudos" @@ -15018,20 +15018,6 @@ *: "No es un plugin" - - id: LANG_OPEN_PLUGIN_SET_WPS_CONTEXT_PLUGIN - desc: open plugin module - user: core - - *: "Set WPS Context Plugin" - - - *: "Establecer Plugin de contexto WPS" - - - *: "Establecer Plugin de contexto WPS" - - id: LANG_PARAMETER desc: @@ -15881,16 +15867,16 @@ id: LANG_DEFAULT_BROWSER - desc: in Settings + desc: deprecated user: core - *: "Default Browser" + *: "" - *: "Navegador predeterminado" + *: "" - *: "Navegador predeterminado" + *: "" @@ -16995,3 +16981,191 @@ *: "~U S B" + + id: LANG_SET_CONTEXT_ITEM + desc: WPS context item + user: core + + *: "Set Context Item" + + + *: "Definir Elemento de Contexto" + + + *: "Definir Elemento de Contexto" + + + + id: LANG_FILE_NOT_FOUND + desc: When file does not exist + user: core + + *: "File not found" + + + *: "Archivo no Encontrado" + + + *: "Archivo no Encontrado" + + + + id: LANG_SHOW_IN_FILES + desc: Reveal item in File Browser + user: core + + *: "Show in Files" + + + *: "Mostrar en Archivos" + + + *: "Mostrar en Archivos" + + + + id: LANG_CHANNEL_SWAP + desc: in sound_settings + user: core + + *: "Swap Left & Right" + + + *: "Intercambiar Izquierda & Derecha" + + + *: "Intercambiar Izquierda & Derecha" + + + + id: LANG_COUNTDOWN_TIMER_SET + desc: countdown_timer plugin - header shown on the setup screen where the user enters the countdown duration + user: core + + *: "SET TIMER" + + + *: "Establecer Temporizador" + + + *: "Establecer Temporizador" + + + + id: LANG_COUNTDOWN_TIMER_RUNNING + desc: countdown_timer plugin - status label shown while the countdown is active + user: core + + *: "RUNNING" + + + *: "En Marcha" + + + *: "En Marcha" + + + + id: LANG_COUNTDOWN_TIMER_PAUSED + desc: countdown_timer plugin - status label shown while the countdown is paused + user: core + + *: "PAUSED" + + + *: "En Pausa" + + + *: "En Pausa" + + + + id: LANG_COUNTDOWN_TIMER_OVERTIME + desc: countdown_timer plugin - status label shown when the countdown has passed zero and is counting up + user: core + + *: "OVERTIME" + + + *: "Extratiempo" + + + *: "Extratiempo" + + + + id: LANG_COUNTDOWN_TIMER_FINISHED + desc: countdown_timer plugin - status label shown at the moment the countdown expires + user: core + + *: "FINISHED" + + + *: "Finalizado" + + + *: "Finalizado" + + + + id: LANG_EQUALIZER_BAND + desc: in the equalizer settings menu + user: core + + *: "Filter %d" + + + *: "Filter %d" + + + *: "Filter" + + + + id: LANG_EQUALIZER_FILTER_TYPE + desc: in the equalizer settings menu + user: core + + *: "Filter Type" + + + *: "Tipo de Filtro" + + + *: "Tipo de Filtro" + + + + id: LANG_COMPOSITE_VIDEO_OUTPUT + desc: Enables composite video framebuffer mirroring on supported targets + user: core + + *: none + videoout: "Composite Video Output" + + + *: none + videoout: "Salida de Video Compuesto" + + + *: none + videoout: "Salida de Video Compuesto" + + + + id: LANG_TOUCHSCREEN_FLICK_SHORTCUTS + desc: in settings menu + user: core + + *: none + touchscreen: "Touchscreen Flick Shortcuts" + + + *: none + touchscreen: "Atajos del Flick Tactil" + + + *: none + touchscreen: "Atajos del Flick Tactil" + + diff --git a/apps/lang/italiano.lang b/apps/lang/italiano.lang index 2746eab39c..ebcff7b644 100644 --- a/apps/lang/italiano.lang +++ b/apps/lang/italiano.lang @@ -1007,13 +1007,13 @@ desc: in the equalizer settings menu user: core - *: "Peak Filter %d" + *: "Peak Filter" - *: "Filtro Picco %d" + *: "Filtro Picco" - *: "Filtro picco" + *: "Filtro Picco" @@ -9088,15 +9088,15 @@ user: core *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Bass Cutoff" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Bass Cutoff" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Taglio Bassi" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Taglio Bassi" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Taglio Bassi" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Taglio Bassi" @@ -9105,15 +9105,15 @@ user: core *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Treble Cutoff" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Treble Cutoff" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Taglio Alti" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Taglio Alti" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Taglio Alti" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Taglio Alti" @@ -17099,3 +17099,65 @@ *: "Imposta Elemento Contestuale" + + id: LANG_EQUALIZER_BAND + desc: in the equalizer settings menu + user: core + + *: "Filter %d" + + + *: "Filtro %d" + + + *: "Filtro" + + + + id: LANG_EQUALIZER_FILTER_TYPE + desc: in the equalizer settings menu + user: core + + *: "Filter Type" + + + *: "Tipo Filtro" + + + *: "Tipo Filtro" + + + + id: LANG_COMPOSITE_VIDEO_OUTPUT + desc: Enables composite video framebuffer mirroring on supported targets + user: core + + *: none + videoout: "Composite Video Output" + + + *: none + videoout: "Uscita Video Composito" + + + *: none + videoout: "Uscita Video Composito" + + + + id: LANG_TOUCHSCREEN_FLICK_SHORTCUTS + desc: in settings menu + user: core + + *: none + touchscreen: "Touchscreen Flick Shortcuts" + + + *: none + touchscreen: "Scorciatoie mediante lo scorrimento sul touchscreen" + + + *: none + touchscreen: "Scorciatoie mediante lo scorrimento sul touchscreen" + + diff --git a/apps/lang/lang.make b/apps/lang/lang.make index 2952ac5aef..5923597e86 100644 --- a/apps/lang/lang.make +++ b/apps/lang/lang.make @@ -62,7 +62,10 @@ $(BUILDDIR)/apps/lang/lang-enum.txt: $(BUILDDIR)/lang_enum.h $(BUILDDIR)/apps/lang/voicestrings.zip: $(VOICEOBJ) $(wildcard $(BUILDDIR)/apps/lang/*.talk) $(BUILDDIR)/apps/lang/voice-corrections.txt $(BUILDDIR)/apps/lang/lang-enum.txt $(call PRINTS,ZIP $(subst $(BUILDDIR)/,,$@)) - $(SILENT)zip -9 -q $@ $(subst $(BUILDDIR)/,,$^) + $(SILENT)zip -X -9 -q $@ $(subst $(BUILDDIR)/,,$^) +ifneq ($(REPRODUCIBLE_ZIP),) + $(SILENT)strip-nondeterminism $@ +endif #copy any included talk files to the /lang directory $(BUILDDIR)/apps/lang/%.talk: $(ROOTDIR)/apps/lang/%.talk diff --git a/apps/lang/latviesu.lang b/apps/lang/latviesu.lang index ab4c8dccd8..f5dbfd7744 100644 --- a/apps/lang/latviesu.lang +++ b/apps/lang/latviesu.lang @@ -1006,10 +1006,10 @@ desc: in the equalizer settings menu user: core - *: "Peak Filter %d" + *: "Peak Filter" - *: "Pīķa filtrs %d" + *: "Pīķa filtrs" *: "Pīķa filtrs" @@ -17098,3 +17098,65 @@ *: "Iestatīt konteksta vienumu" + + id: LANG_EQUALIZER_BAND + desc: in the equalizer settings menu + user: core + + *: "Filter %d" + + + *: "Filtrs %d" + + + *: "Filtrs" + + + + id: LANG_EQUALIZER_FILTER_TYPE + desc: in the equalizer settings menu + user: core + + *: "Filter Type" + + + *: "Filtra veids" + + + *: "Filtra veids" + + + + id: LANG_COMPOSITE_VIDEO_OUTPUT + desc: Enables composite video framebuffer mirroring on supported targets + user: core + + *: none + videoout: "Composite Video Output" + + + *: none + videoout: "Kompozītvideo izeja" + + + *: none + videoout: "Kompozītvideo izeja" + + + + id: LANG_TOUCHSCREEN_FLICK_SHORTCUTS + desc: in settings menu + user: core + + *: none + touchscreen: "Touchscreen Flick Shortcuts" + + + *: none + touchscreen: "Skārienekrāna žestu saīsnes" + + + *: none + touchscreen: "Skārienekrāna žestu saīsnes" + + diff --git a/apps/lang/magyar.lang b/apps/lang/magyar.lang index 6383dc0480..31a324c04b 100644 --- a/apps/lang/magyar.lang +++ b/apps/lang/magyar.lang @@ -991,10 +991,10 @@ desc: in the equalizer settings menu user: core - *: "Peak Filter %d" + *: "Peak Filter" - *: "Csúcsszűrő %d" + *: "Csúcsszűrő" *: "Csúcsszűrő" @@ -15054,20 +15054,6 @@ *: "Nem bővítmény" - - id: LANG_OPEN_PLUGIN_SET_WPS_CONTEXT_PLUGIN - desc: open plugin module - user: core - - *: "Set WPS Context Plugin" - - - *: "WPS Context Plugin beállítása" - - - *: "WPS Context Plugin beállítása" - - id: LANG_PARAMETER desc: @@ -15308,13 +15294,13 @@ id: LANG_VOICED_DATE_FORMAT - desc: format string for how dates will be read back. Y == 4-digit year, A == month name, m == numeric month, d == numeric day. For example, "AdY" will read "January 21 2021" + desc: format string for how dates will be read back. Y == 4-digit year (grouped), y == 4-digit year (numeric), A == month name, m == numeric month, d == numeric day. For example, for 2021-01-05, "AdY" will be voiced as "January 5 twenty twenty-one" and "dmy" will be voiced as "5 1 two thousand twenty one user: core *: "dAY" - *: "dAY" + *: "~dAY" *: "" @@ -15917,16 +15903,16 @@ id: LANG_DEFAULT_BROWSER - desc: in Settings + desc: deprecated user: core - *: "Default Browser" + *: "" - *: "Alapértelmezett böngésző" + *: "" - *: "Alapértelmezett böngésző" + *: "" @@ -16975,3 +16961,171 @@ *: "~U S B" + + id: LANG_SET_CONTEXT_ITEM + desc: WPS context item + user: core + + *: "Set Context Item" + + + *: "Kontextus elem beállítása" + + + *: "Kontextus elem beállítása" + + + + id: LANG_KEEP_DIRECTORY + desc: file browser setting + user: core + + *: "Always remember last folder" + + + *: "Mindig emlékezzen a legutóbbi mappára" + + + *: "Mindig emlékezzen a legutóbbi mappára" + + + + id: LANG_FILE_NOT_FOUND + desc: When file does not exist + user: core + + *: "File not found" + + + *: "Fájl nem található" + + + *: "Fájl nem található" + + + + id: LANG_SHOW_IN_FILES + desc: Reveal item in File Browser + user: core + + *: "Show in Files" + + + *: "Mutatás a Fájlokban" + + + *: "Mutatás a Fájlokban" + + + + id: LANG_CHANNEL_SWAP + desc: in sound_settings + user: core + + *: "Swap Left & Right" + + + *: "Bal & Jobb megcserélése" + + + *: "Bal & Jobb megcserélése" + + + + id: LANG_COUNTDOWN_TIMER_SET + desc: countdown_timer plugin - header shown on the setup screen where the user enters the countdown duration + user: core + + *: "SET TIMER" + + + *: "IDŐZÍTŐ BEÁLLÍTÁSA" + + + *: "Időzítő beállítása" + + + + id: LANG_COUNTDOWN_TIMER_RUNNING + desc: countdown_timer plugin - status label shown while the countdown is active + user: core + + *: "RUNNING" + + + *: "FUT" + + + *: "Fut" + + + + id: LANG_COUNTDOWN_TIMER_PAUSED + desc: countdown_timer plugin - status label shown while the countdown is paused + user: core + + *: "PAUSED" + + + *: "MEGÁLLÍTVA" + + + *: "Megállítva" + + + + id: LANG_COUNTDOWN_TIMER_OVERTIME + desc: countdown_timer plugin - status label shown when the countdown has passed zero and is counting up + user: core + + *: "OVERTIME" + + + *: "TÚLÓRA" + + + *: "Túlóra" + + + + id: LANG_COUNTDOWN_TIMER_FINISHED + desc: countdown_timer plugin - status label shown at the moment the countdown expires + user: core + + *: "FINISHED" + + + *: "KÉSZ" + + + *: "Kész" + + + + id: LANG_EQUALIZER_BAND + desc: in the equalizer settings menu + user: core + + *: "Filter %d" + + + *: "Szűrő %d" + + + *: "Szűrő" + + + + id: LANG_EQUALIZER_FILTER_TYPE + desc: in the equalizer settings menu + user: core + + *: "Filter Type" + + + *: "Szűrő típusa" + + + *: "Szűrő típusa" + + diff --git a/apps/lang/moldoveneste.lang b/apps/lang/moldoveneste.lang index 4c8b371ff3..6ce1de413d 100644 --- a/apps/lang/moldoveneste.lang +++ b/apps/lang/moldoveneste.lang @@ -2231,15 +2231,15 @@ user: core *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Bass Cutoff" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Bass Cutoff" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Retezare basuri" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Tăiere bas" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Retezare basuri" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Tăiere bas" @@ -2308,11 +2308,11 @@ *: none - lcd_color: "Culoare primară " + lcd_color: "Culoare primară" *: none - lcd_color: "Culoare primară " + lcd_color: "Culoare primară" @@ -6841,10 +6841,10 @@ *: "%d Hz Band Gain" - *: "%d Hz bandă amplificare" + *: "Cîștig bandă %d Hz" - *: "hertz bandă amplificare" + *: "Cîștig bandă" @@ -8146,10 +8146,10 @@ *: "High Shelf Filter" - *: "Filtru frecvențe ridicate" + *: "Filtru frecvențe înalte" - *: "Filtru frecvențe ridicate" + *: "Filtru frecvențe înalte" @@ -8279,15 +8279,15 @@ user: core *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Treble Cutoff" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Treble Cutoff" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Tăiere inalte" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Tăiere inalte" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Tăiere inalte" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Tăiere inalte" @@ -8845,13 +8845,13 @@ desc: in the equalizer settings menu user: core - *: "Peak Filter %d" + *: "Peak Filter" - *: "Filtru vîrfuri %d" + *: "Filtru vîrf" - *: "Filtru vîrfuri" + *: "Filtru vîrf" @@ -10587,7 +10587,7 @@ *: none - remote: "aparte pentru bara de stare " + remote: "aparte pentru bara de stare" @@ -11173,7 +11173,7 @@ *: none - histogram: "Interval histogramă " + histogram: "Interval histogramă" @@ -17104,3 +17104,65 @@ *: "Setează element context" + + id: LANG_EQUALIZER_BAND + desc: in the equalizer settings menu + user: core + + *: "Filter %d" + + + *: "Filtru %d" + + + *: "Filtru" + + + + id: LANG_EQUALIZER_FILTER_TYPE + desc: in the equalizer settings menu + user: core + + *: "Filter Type" + + + *: "Tip filtru" + + + *: "Tip filtru" + + + + id: LANG_COMPOSITE_VIDEO_OUTPUT + desc: Enables composite video framebuffer mirroring on supported targets + user: core + + *: none + videoout: "Composite Video Output" + + + *: none + videoout: "Ieșire video compozit" + + + *: none + videoout: "Ieșire video compozit" + + + + id: LANG_TOUCHSCREEN_FLICK_SHORTCUTS + desc: in settings menu + user: core + + *: none + touchscreen: "Touchscreen Flick Shortcuts" + + + *: none + touchscreen: "Scurtături gesturi touchscreen" + + + *: none + touchscreen: "Scurtături gesturi taciscrin" + + diff --git a/apps/lang/nederlands.lang b/apps/lang/nederlands.lang index aea94e33ae..398e62d26f 100644 --- a/apps/lang/nederlands.lang +++ b/apps/lang/nederlands.lang @@ -737,10 +737,10 @@ desc: in the equalizer settings menu user: core - *: "Peak Filter %d" + *: "Peak Filter" - *: "Piekfilter %d" + *: "Peak Filter" *: "Piekfilter" @@ -14434,7 +14434,7 @@ user: core *: "Press LEFT to cancel." - android,hifietma*,zenvision: "Press BACK to cancel." + android,hifietma*: "Press BACK to cancel." cowond2,creativezenxfi2,ibassodx50,ibassodx90,mrobe500,ondavx747: "Press POWER to cancel." ihifi760,ihifi960: "Double tap RETURN to cancel." ihifi770,ihifi770c,ihifi800: "Press HOME to cancel." @@ -14450,7 +14450,7 @@ *: "Druk op LINKS om te annuleren." android: "Druk op RECHTS om te annuleren." - hifietma*,zenvision: "Druk op BACK om te annuleren." + hifietma*: "Druk op BACK om te annuleren." cowond2,creativezenxfi2,ibassodx50,ibassodx90,mrobe500,ondavx747: "Druk op POWER om te annuleren." ihifi760,ihifi960: "Dubbeltik op RETURN om te annuleren." ihifi770,ihifi770c,ihifi800: "Druk op HOME om te annuleren." @@ -14466,7 +14466,7 @@ *: "Druk op LINKS om te annuleren." android: "Druk op RECHTS om te annuleren." - hifietma*,zenvision: "Druk op BACK om te annuleren." + hifietma*: "Druk op BACK om te annuleren." cowond2,creativezenxfi2,ibassodx50,ibassodx90,mrobe500,ondavx747: "Druk op POWER om te annuleren." ihifi760,ihifi960: "Dubbeltik op RETURN om te annuleren." ihifi770,ihifi770c,ihifi800: "Druk op HOME om te annuleren." @@ -14707,20 +14707,6 @@ *: "Geen plug-in" - - id: LANG_OPEN_PLUGIN_SET_WPS_CONTEXT_PLUGIN - desc: open plugin module - user: core - - *: "Set WPS Context Plugin" - - - *: "Stel WPS Context Plugin in" - - - *: "Stel WPS Context Plugin in" - - id: LANG_PARAMETER desc: @@ -15202,7 +15188,7 @@ id: LANG_VOICED_DATE_FORMAT - desc: format string for how dates will be read back. Y == 4-digit year, A == month name, m == numeric month, d == numeric day. For example, "AdY" will read "January 21 2021" + desc: format string for how dates will be read back. Y == 4-digit year (grouped), y == 4-digit year (numeric), A == month name, m == numeric month, d == numeric day. For example, for 2021-01-05, "AdY" will be voiced as "January 5 twenty twenty-one" and "dmy" will be voiced as "5 1 two thousand twenty one user: core *: "dAY" @@ -15811,16 +15797,16 @@ id: LANG_DEFAULT_BROWSER - desc: in Settings + desc: deprecated user: core - *: "Default Browser" + *: "" - *: "Standaard Browser" + *: "" - *: "Standaard Browser" + *: "" @@ -16967,3 +16953,185 @@ *: "~U S B" + + id: LANG_ANNOUNCE_STATUS + desc: announnnce_status plugin + user: core + + *: "Announce Status" + + + *: "Status aankondigen" + + + *: "Status aankondigen" + + + + id: LANG_SET_CONTEXT_ITEM + desc: WPS context item + user: core + + *: "Set Context Item" + + + *: "Contextitem instellen" + + + *: "Contextitem instellen" + + + + id: LANG_KEEP_DIRECTORY + desc: file browser setting + user: core + + *: "Always remember last folder" + + + *: "Altijd laatste map onthouden" + + + *: "Altijd laatste map onthouden" + + + + id: LANG_FILE_NOT_FOUND + desc: When file does not exist + user: core + + *: "File not found" + + + *: "Bestand niet gevonden" + + + *: "Bestand niet gevonden" + + + + id: LANG_SHOW_IN_FILES + desc: Reveal item in File Browser + user: core + + *: "Show in Files" + + + *: "In bestanden tonen" + + + *: "In bestanden tonen" + + + + id: LANG_CHANNEL_SWAP + desc: in sound_settings + user: core + + *: "Swap Left & Right" + + + *: "Links & Rechts omwisselen" + + + *: "Links & Rechts omwisselen" + + + + id: LANG_COUNTDOWN_TIMER_SET + desc: countdown_timer plugin - header shown on the setup screen where the user enters the countdown duration + user: core + + *: "SET TIMER" + + + *: "TIMER INSTELLEN" + + + *: "Timer instellen" + + + + id: LANG_COUNTDOWN_TIMER_RUNNING + desc: countdown_timer plugin - status label shown while the countdown is active + user: core + + *: "RUNNING" + + + *: "ACTIEF" + + + *: "Actief" + + + + id: LANG_COUNTDOWN_TIMER_PAUSED + desc: countdown_timer plugin - status label shown while the countdown is paused + user: core + + *: "PAUSED" + + + *: "GEPAUZEERD" + + + *: "Gepauzeerd" + + + + id: LANG_COUNTDOWN_TIMER_OVERTIME + desc: countdown_timer plugin - status label shown when the countdown has passed zero and is counting up + user: core + + *: "OVERTIME" + + + *: "OVER TIJD" + + + *: "Over tijd" + + + + id: LANG_COUNTDOWN_TIMER_FINISHED + desc: countdown_timer plugin - status label shown at the moment the countdown expires + user: core + + *: "FINISHED" + + + *: "AFGELOPEN" + + + *: "Afgelopen" + + + + id: LANG_EQUALIZER_BAND + desc: in the equalizer settings menu + user: core + + *: "Filter %d" + + + *: "Filter %d" + + + *: "Filter" + + + + id: LANG_EQUALIZER_FILTER_TYPE + desc: in the equalizer settings menu + user: core + + *: "Filter Type" + + + *: "Filter Type" + + + *: "Filter Type" + + diff --git a/apps/lang/polski.lang b/apps/lang/polski.lang index 2ed705ffd6..8751ffa555 100644 --- a/apps/lang/polski.lang +++ b/apps/lang/polski.lang @@ -1014,13 +1014,13 @@ desc: in the equalizer settings menu user: core - *: "Peak Filter %d" + *: "Peak Filter" - *: "Filtr pasmowy %d" + *: "Filtr szczytowy" - *: "Filtr pasmowy" + *: "Filtr szczytowy" @@ -9095,15 +9095,15 @@ user: core *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Bass Cutoff" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Bass Cutoff" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Ścięcie basów" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Granica basu" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Ścięcie basów" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Granica basu" @@ -9112,15 +9112,15 @@ user: core *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Treble Cutoff" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Treble Cutoff" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Ścięcie tonów niskich" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Granica tonów wysokich" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Ścięcie tonów niskich" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Granica tonów wysokich" @@ -17106,3 +17106,65 @@ *: "Ustaw element kontekstowy" + + id: LANG_EQUALIZER_BAND + desc: in the equalizer settings menu + user: core + + *: "Filter %d" + + + *: "Filtr %d" + + + *: "Filtr" + + + + id: LANG_EQUALIZER_FILTER_TYPE + desc: in the equalizer settings menu + user: core + + *: "Filter Type" + + + *: "Typ filtra" + + + *: "Typ filtra" + + + + id: LANG_COMPOSITE_VIDEO_OUTPUT + desc: Enables composite video framebuffer mirroring on supported targets + user: core + + *: none + videoout: "Composite Video Output" + + + *: none + videoout: "Wyjście wideo kompozytowe" + + + *: none + videoout: "Wyjście wideo kompozytowe" + + + + id: LANG_TOUCHSCREEN_FLICK_SHORTCUTS + desc: in settings menu + user: core + + *: none + touchscreen: "Touchscreen Flick Shortcuts" + + + *: none + touchscreen: "Skróty ekranu dotykowego" + + + *: none + touchscreen: "Skróty ekranu dotykowego" + + diff --git a/apps/lang/romaneste.lang b/apps/lang/romaneste.lang index dbae60f9ba..8f54582a1f 100644 --- a/apps/lang/romaneste.lang +++ b/apps/lang/romaneste.lang @@ -2231,15 +2231,15 @@ user: core *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Bass Cutoff" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Bass Cutoff" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Retezare basuri" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Tăiere bas" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Retezare basuri" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Tăiere bas" @@ -2308,11 +2308,11 @@ *: none - lcd_color: "Culoare primară " + lcd_color: "Culoare primară" *: none - lcd_color: "Culoare primară " + lcd_color: "Culoare primară" @@ -6841,10 +6841,10 @@ *: "%d Hz Band Gain" - *: "%d Hz bandă amplificare" + *: "Câștig bandă %d Hz" - *: "hertz bandă amplificare" + *: "Câștig bandă" @@ -8146,10 +8146,10 @@ *: "High Shelf Filter" - *: "Filtru frecvențe ridicate" + *: "Filtru frecvențe înalte" - *: "Filtru frecvențe ridicate" + *: "Filtru frecvențe înalte" @@ -8279,15 +8279,15 @@ user: core *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Treble Cutoff" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Treble Cutoff" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Tăiere inalte" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Tăiere înalte" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Tăiere inalte" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Tăiere înalte" @@ -8845,13 +8845,13 @@ desc: in the equalizer settings menu user: core - *: "Peak Filter %d" + *: "Peak Filter" - *: "Filtru vârfuri %d" + *: "Filtru vârf" - *: "Filtru vârfuri" + *: "Filtru vârf" @@ -10587,7 +10587,7 @@ *: none - remote: "aparte pentru bara de stare " + remote: "aparte pentru bara de stare" @@ -11173,7 +11173,7 @@ *: none - histogram: "Interval histogramă " + histogram: "Interval histogramă" @@ -17104,3 +17104,65 @@ *: "Setează element context" + + id: LANG_EQUALIZER_BAND + desc: in the equalizer settings menu + user: core + + *: "Filter %d" + + + *: "Filtru %d" + + + *: "Filtru" + + + + id: LANG_EQUALIZER_FILTER_TYPE + desc: in the equalizer settings menu + user: core + + *: "Filter Type" + + + *: "Tip filtru" + + + *: "Tip filtru" + + + + id: LANG_COMPOSITE_VIDEO_OUTPUT + desc: Enables composite video framebuffer mirroring on supported targets + user: core + + *: none + videoout: "Composite Video Output" + + + *: none + videoout: "Ieșire video compozit" + + + *: none + videoout: "Ieșire video compozit" + + + + id: LANG_TOUCHSCREEN_FLICK_SHORTCUTS + desc: in settings menu + user: core + + *: none + touchscreen: "Touchscreen Flick Shortcuts" + + + *: none + touchscreen: "Scurtături gesturi touchscreen" + + + *: none + touchscreen: "Scurtături gesturi taciscrin" + + diff --git a/apps/lang/russian.lang b/apps/lang/russian.lang index 47feb49e30..c8da55dc91 100644 --- a/apps/lang/russian.lang +++ b/apps/lang/russian.lang @@ -32,6 +32,7 @@ # - Dmitry Prozorov # - Fedor Pomazkov # - Vladislav Voronichev +# - Sergey Puskov # - Ivan Romaniuk-Mikhailovsky id: LANG_SET_BOOL_YES @@ -593,10 +594,10 @@ *: "Save .cfg File" - *: "Запись профиля настроек" + *: "Сохранить профиль настроек" - *: "Запись профиля настроек" + *: "Сохранить профиль настроек" @@ -733,13 +734,13 @@ desc: in the equalizer settings menu user: core - *: "Peak Filter %d" + *: "Peak Filter" - *: "Полосовой фильтр %d" + *: "Пиковый фильтр" - *: "Полосовой фильтр" + *: "Пиковый фильтр" @@ -14646,20 +14647,6 @@ *: "Не плагин" - - id: LANG_OPEN_PLUGIN_SET_WPS_CONTEXT_PLUGIN - desc: open plugin module - user: core - - *: "Set WPS Context Plugin" - - - *: "Назначить плагин режима воспроизведения" - - - *: "Назначить плагин режима воспроизведения" - - id: LANG_PARAMETER desc: @@ -15846,16 +15833,16 @@ id: LANG_DEFAULT_BROWSER - desc: in Settings + desc: deprecated user: core - *: "Default Browser" + *: "" - *: "Браузер по умолчанию" + *: "" - *: "Браузер по умолчанию" + *: "" @@ -16951,10 +16938,10 @@ *: "Announce Status" - *: "Объявлять статус" + *: "Объявление статуса" - *: "Объявлять статус" + *: "Объявление статуса" @@ -17002,3 +16989,157 @@ *: "Ю С Б" + + id: LANG_SET_CONTEXT_ITEM + desc: WPS context item + user: core + + *: "Set Context Item" + + + *: "Выбрать пункт меню" + + + *: "Выбрать пункт меню" + + + + id: LANG_FILE_NOT_FOUND + desc: When file does not exist + user: core + + *: "File not found" + + + *: "Файл не найден" + + + *: "Файл не найден" + + + + id: LANG_SHOW_IN_FILES + desc: Reveal item in File Browser + user: core + + *: "Show in Files" + + + *: "Показать в файлах" + + + *: "Показать в файлах" + + + + id: LANG_CHANNEL_SWAP + desc: in sound_settings + user: core + + *: "Swap Left & Right" + + + *: "Поменять местами" + + + *: "Поменять местами" + + + + id: LANG_COUNTDOWN_TIMER_SET + desc: countdown_timer plugin - header shown on the setup screen where the user enters the countdown duration + user: core + + *: "SET TIMER" + + + *: "УСТАНОВИТЬ ТАЙМЕР" + + + *: "Установить таймер" + + + + id: LANG_COUNTDOWN_TIMER_RUNNING + desc: countdown_timer plugin - status label shown while the countdown is active + user: core + + *: "RUNNING" + + + *: "РАБОТАЕТ" + + + *: "Работает" + + + + id: LANG_COUNTDOWN_TIMER_PAUSED + desc: countdown_timer plugin - status label shown while the countdown is paused + user: core + + *: "PAUSED" + + + *: "ПАУЗА" + + + *: "Пауза" + + + + id: LANG_COUNTDOWN_TIMER_OVERTIME + desc: countdown_timer plugin - status label shown when the countdown has passed zero and is counting up + user: core + + *: "OVERTIME" + + + *: "ВРЕМЯ ПОСЛЕ СРАБАТЫВАНИЯ" + + + *: "Время после срабатывания" + + + + id: LANG_COUNTDOWN_TIMER_FINISHED + desc: countdown_timer plugin - status label shown at the moment the countdown expires + user: core + + *: "FINISHED" + + + *: "ВРЕМЯ ВЫШЛО" + + + *: "Время вышло" + + + + id: LANG_EQUALIZER_BAND + desc: in the equalizer settings menu + user: core + + *: "Filter %d" + + + *: "Фильтр %d" + + + *: "Фильтр" + + + + id: LANG_EQUALIZER_FILTER_TYPE + desc: in the equalizer settings menu + user: core + + *: "Filter Type" + + + *: "Тип фильтра" + + + *: "Тип фильтра" + + diff --git a/apps/lang/slovak.lang b/apps/lang/slovak.lang index 77020f4191..d44cd4ee08 100644 --- a/apps/lang/slovak.lang +++ b/apps/lang/slovak.lang @@ -1005,13 +1005,13 @@ desc: in the equalizer settings menu user: core - *: "Peak Filter %d" + *: "Peak Filter" - *: "Filter pásma %d" + *: "Vrcholový filter" - *: "Filter pásma" + *: "Vrcholový filter" @@ -9086,15 +9086,15 @@ user: core *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Bass Cutoff" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Bass Cutoff" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Hranica hĺbok" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Hranica hĺbok" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Hranica hĺbok" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Hranica hĺbok" @@ -9103,15 +9103,15 @@ user: core *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Treble Cutoff" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Treble Cutoff" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Hranica výšok" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Hranica výšok" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Hranica výšok" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Hranica výšok" @@ -17099,3 +17099,65 @@ *: "Nastaviť kontextovú položku" + + id: LANG_EQUALIZER_BAND + desc: in the equalizer settings menu + user: core + + *: "Filter %d" + + + *: "~Filter %d" + + + *: "~Filter" + + + + id: LANG_EQUALIZER_FILTER_TYPE + desc: in the equalizer settings menu + user: core + + *: "Filter Type" + + + *: "Typ filtra" + + + *: "Typ filtra" + + + + id: LANG_COMPOSITE_VIDEO_OUTPUT + desc: Enables composite video framebuffer mirroring on supported targets + user: core + + *: none + videoout: "Composite Video Output" + + + *: none + videoout: "Kompozitný video výstup" + + + *: none + videoout: "Kompozitný video výstup" + + + + id: LANG_TOUCHSCREEN_FLICK_SHORTCUTS + desc: in settings menu + user: core + + *: none + touchscreen: "Touchscreen Flick Shortcuts" + + + *: none + touchscreen: "Skratky švihnutia na obrazovke" + + + *: none + touchscreen: "Skratky švihnutia na obrazovke" + + diff --git a/apps/lang/turkce.lang b/apps/lang/turkce.lang index 6daad2e8cb..374a89ea82 100644 --- a/apps/lang/turkce.lang +++ b/apps/lang/turkce.lang @@ -5426,15 +5426,15 @@ user: core *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Treble Cutoff" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Treble Cutoff" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Tiz Kesme" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Tiz Kesme" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Tiz Kesme" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Tiz Kesme" @@ -8629,10 +8629,10 @@ desc: in the equalizer settings menu user: core - *: "Peak Filter %d" + *: "Peak Filter" - *: "Tepe Filtresi %d" + *: "Tepe Filtresi" *: "Tepe Filtresi" @@ -9502,15 +9502,15 @@ user: core *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Bass Cutoff" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Bass Cutoff" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Bas Kesme" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Bas Kesme" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Bas Kesme" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Bas Kesme" @@ -14941,20 +14941,6 @@ *: "Eklenti değil" - - id: LANG_OPEN_PLUGIN_SET_WPS_CONTEXT_PLUGIN - desc: open plugin module - user: core - - *: "Set WPS Context Plugin" - - - *: "WPS Bağlam Eklentisini Ayarla" - - - *: "WPS Bağlam Eklentisini Ayarla" - - id: LANG_PARAMETER desc: @@ -15804,16 +15790,16 @@ id: LANG_DEFAULT_BROWSER - desc: in Settings + desc: deprecated user: core - *: "Default Browser" + *: "" - *: "Varsayılan Tarayıcı" + *: "" - *: "Varsayılan Tarayıcı" + *: "" @@ -16988,3 +16974,191 @@ *: "~U S B" + + id: LANG_SET_CONTEXT_ITEM + desc: WPS context item + user: core + + *: "Set Context Item" + + + *: "Öğeyi Ayarla" + + + *: "Öğeyi Ayarla" + + + + id: LANG_FILE_NOT_FOUND + desc: When file does not exist + user: core + + *: "File not found" + + + *: "Dosya bulunamadı" + + + *: "Dosya bulunamadı" + + + + id: LANG_SHOW_IN_FILES + desc: Reveal item in File Browser + user: core + + *: "Show in Files" + + + *: "Dosyalarda Göster" + + + *: "Dosyalarda Göster" + + + + id: LANG_CHANNEL_SWAP + desc: in sound_settings + user: core + + *: "Swap Left & Right" + + + *: "Sol & Sağı Değiştir" + + + *: "Sol & Sağı Değiştir" + + + + id: LANG_COUNTDOWN_TIMER_SET + desc: countdown_timer plugin - header shown on the setup screen where the user enters the countdown duration + user: core + + *: "SET TIMER" + + + *: "ZAMANLAYICI AYARLA" + + + *: "Zamanlayıcı ayarla" + + + + id: LANG_COUNTDOWN_TIMER_RUNNING + desc: countdown_timer plugin - status label shown while the countdown is active + user: core + + *: "RUNNING" + + + *: "ÇALIŞIYOR" + + + *: "Çalışıyor" + + + + id: LANG_COUNTDOWN_TIMER_PAUSED + desc: countdown_timer plugin - status label shown while the countdown is paused + user: core + + *: "PAUSED" + + + *: "DURDURULDU" + + + *: "Durduruldu" + + + + id: LANG_COUNTDOWN_TIMER_OVERTIME + desc: countdown_timer plugin - status label shown when the countdown has passed zero and is counting up + user: core + + *: "OVERTIME" + + + *: "SÜRE AŞIMI" + + + *: "Süre Aşımı" + + + + id: LANG_COUNTDOWN_TIMER_FINISHED + desc: countdown_timer plugin - status label shown at the moment the countdown expires + user: core + + *: "FINISHED" + + + *: "BİTTİ" + + + *: "Bitti" + + + + id: LANG_EQUALIZER_BAND + desc: in the equalizer settings menu + user: core + + *: "Filter %d" + + + *: "Filtre %d" + + + *: "Filtre" + + + + id: LANG_EQUALIZER_FILTER_TYPE + desc: in the equalizer settings menu + user: core + + *: "Filter Type" + + + *: "Filtre Tipi" + + + *: "Filtre Tipi" + + + + id: LANG_COMPOSITE_VIDEO_OUTPUT + desc: Enables composite video framebuffer mirroring on supported targets + user: core + + *: none + videoout: "Composite Video Output" + + + *: none + videoout: "Kompozit Video Çıkışı" + + + *: none + videoout: "Kompozit Video Çıkışı" + + + + id: LANG_TOUCHSCREEN_FLICK_SHORTCUTS + desc: in settings menu + user: core + + *: none + touchscreen: "Touchscreen Flick Shortcuts" + + + *: none + touchscreen: "Ekran Kaydırma Kısayolları" + + + *: none + touchscreen: "Ekran Kaydırma Kısayolları" + + diff --git a/apps/lang/vietnamese.lang b/apps/lang/vietnamese.lang index ea8190f9f5..5b93b54f84 100644 --- a/apps/lang/vietnamese.lang +++ b/apps/lang/vietnamese.lang @@ -1006,10 +1006,10 @@ desc: in the equalizer settings menu user: core - *: "Peak Filter %d" + *: "Peak Filter" - *: "~Peak Filter %d" + *: "~Peak Filter" *: "~Peak filter" @@ -9139,15 +9139,15 @@ user: core *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Bass Cutoff" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Bass Cutoff" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Khoảng cắt âm trầm" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Khoảng cắt âm trầm" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Khoảng cắt âm trầm" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Khoảng cắt âm trầm" @@ -9156,15 +9156,15 @@ user: core *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Treble Cutoff" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Treble Cutoff" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Khoảng cắt âm cao" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Khoảng cắt âm cao" *: none - gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100: "Khoảng cắt âm cao" + gigabeatfx,ipod6g,ipodnano3g,ipodnano4g,ipodvideo,mpiohd200,mpiohd300,mrobe100,samsungypcp3: "Khoảng cắt âm cao" @@ -14689,7 +14689,7 @@ user: core *: "Press LEFT to cancel." - android,hifietma*,zenvision: "Press BACK to cancel." + android,hifietma*: "Press BACK to cancel." cowond2,creativezenxfi2,ibassodx50,ibassodx90,mrobe500,ondavx747: "Press POWER to cancel." ihifi760,ihifi960: "Double tap RETURN to cancel." ihifi770,ihifi770c,ihifi800: "Press HOME to cancel." @@ -14704,7 +14704,7 @@ *: "Nhấn LEFT để hủy." - android,hifietma*,zenvision: "Nhấn BACK để hủy." + android,hifietma*: "Nhấn BACK để hủy." cowond2,creativezenxfi2,ibassodx50,ibassodx90,mrobe500,ondavx747: "Nhấn POWER để hủy." ihifi760,ihifi960: "Nhấn đúp RETURN để hủy." ihifi770,ihifi770c,ihifi800: "Nhấn HOME để hủy." @@ -14719,7 +14719,7 @@ *: "Nhấn LEFT để hủy." - android,hifietma*,zenvision: "Nhấn BACK để hủy." + android,hifietma*: "Nhấn BACK để hủy." cowond2,creativezenxfi2,ibassodx50,ibassodx90,mrobe500,ondavx747: "Nhấn POWER để hủy." ihifi760,ihifi960: "Nhấn đúp RETURN để hủy." ihifi770,ihifi770c,ihifi800: "Nhấn HOME để hủy." @@ -14960,20 +14960,6 @@ *: "Không phải là plugin" - - id: LANG_OPEN_PLUGIN_SET_WPS_CONTEXT_PLUGIN - desc: open plugin module - user: core - - *: "Set WPS Context Plugin" - - - *: "Đặt ngữ cảnh Wps của Plugin" - - - *: "Đặt ngữ cảnh WPS của Plugin" - - id: LANG_PARAMETER desc: @@ -15214,7 +15200,7 @@ id: LANG_VOICED_DATE_FORMAT - desc: format string for how dates will be read back. Y == 4-digit year, A == month name, m == numeric month, d == numeric day. For example, "AdY" will read "January 21 2021" + desc: format string for how dates will be read back. Y == 4-digit year (grouped), y == 4-digit year (numeric), A == month name, m == numeric month, d == numeric day. For example, for 2021-01-05, "AdY" will be voiced as "January 5 twenty twenty-one" and "dmy" will be voiced as "5 1 two thousand twenty one user: core *: "dAY" @@ -15823,16 +15809,16 @@ id: LANG_DEFAULT_BROWSER - desc: in Settings + desc: deprecated user: core - *: "Default Browser" + *: "" - *: "Trình duyệt mặc định" + *: "" - *: "Trình duyệt mặc định" + *: "" @@ -16971,3 +16957,205 @@ *: "~U S B" + + id: LANG_SET_CONTEXT_ITEM + desc: WPS context item + user: core + + *: "Set Context Item" + + + *: "Đặt Vật phẩm Bối cảnh" + + + *: "Đặt Vật phẩm Bối cảnh" + + + + id: LANG_KEEP_DIRECTORY + desc: file browser setting + user: core + + *: "Always remember last folder" + + + *: "Luôn ghi nhớ thư mục lần trước" + + + *: "Luôn ghi nhớ thư mục lần trước" + + + + id: LANG_FILE_NOT_FOUND + desc: When file does not exist + user: core + + *: "File not found" + + + *: "Không tìm thấy tập tin" + + + *: "Không tìm thấy tập tin" + + + + id: LANG_SHOW_IN_FILES + desc: Reveal item in File Browser + user: core + + *: "Show in Files" + + + *: "Hiển thị trong Tập Tin" + + + *: "Hiển thị trong Tập Tin" + + + + id: LANG_CHANNEL_SWAP + desc: in sound_settings + user: core + + *: "Swap Left & Right" + + + *: "Hoán đổi Trái & Phảii" + + + *: "Hoán đổi Trái & Phảii" + + + + id: LANG_COUNTDOWN_TIMER_SET + desc: countdown_timer plugin - header shown on the setup screen where the user enters the countdown duration + user: core + + *: "SET TIMER" + + + *: "ĐẶT KHOẢNG THỜI GIAN" + + + *: "Đặt khoảng thời gian" + + + + id: LANG_COUNTDOWN_TIMER_RUNNING + desc: countdown_timer plugin - status label shown while the countdown is active + user: core + + *: "RUNNING" + + + *: "ĐANG CHẠY" + + + *: "Đang chạy" + + + + id: LANG_COUNTDOWN_TIMER_PAUSED + desc: countdown_timer plugin - status label shown while the countdown is paused + user: core + + *: "PAUSED" + + + *: "Tạm dừng" + + + *: "Tạm dừng" + + + + id: LANG_COUNTDOWN_TIMER_OVERTIME + desc: countdown_timer plugin - status label shown when the countdown has passed zero and is counting up + user: core + + *: "OVERTIME" + + + *: "QUÁ GIỜ" + + + *: "Quá giờ" + + + + id: LANG_COUNTDOWN_TIMER_FINISHED + desc: countdown_timer plugin - status label shown at the moment the countdown expires + user: core + + *: "FINISHED" + + + *: "HOÀN THÀNH" + + + *: "Hoàn thành" + + + + id: LANG_EQUALIZER_BAND + desc: in the equalizer settings menu + user: core + + *: "Filter %d" + + + *: "Bộ lọc %d" + + + *: "Bộ lọc" + + + + id: LANG_EQUALIZER_FILTER_TYPE + desc: in the equalizer settings menu + user: core + + *: "Filter Type" + + + *: "Kiểu Bộ lọc" + + + *: "Kiểu bộ lọc" + + + + id: LANG_COMPOSITE_VIDEO_OUTPUT + desc: Enables composite video framebuffer mirroring on supported targets + user: core + + *: none + videoout: "Composite Video Output" + + + *: none + videoout: "Đầu ra Video Composite" + + + *: none + videoout: "Đầu ra Video Composite" + + + + id: LANG_TOUCHSCREEN_FLICK_SHORTCUTS + desc: in settings menu + user: core + + *: none + touchscreen: "Touchscreen Flick Shortcuts" + + + *: none + touchscreen: "Lối tắt Nháy Màn hình" + + + *: none + touchscreen: "Lối tắt Nháy Màn hình" + + diff --git a/apps/menu.c b/apps/menu.c index b4c4a3d419..f81c1665a5 100644 --- a/apps/menu.c +++ b/apps/menu.c @@ -472,7 +472,7 @@ int do_menu(const struct menu_item_ex *start_menu, int *start_selected, else if (action == ACTION_STD_QUICKSCREEN) { if (global_settings.shortcuts_replaces_qs || - quick_screen_quick(action) == QUICKSCREEN_GOTO_SHORTCUTS_MENU) + quickscreen_show(action) == QUICKSCREEN_GOTO_SHORTCUTS_MENU) { int last_screen = global_status.last_screen; global_status.last_screen = GO_TO_SHORTCUTMENU; diff --git a/apps/menus/display_menu.c b/apps/menus/display_menu.c index f7b3efa2c4..99b1ad5f1e 100644 --- a/apps/menus/display_menu.c +++ b/apps/menus/display_menu.c @@ -180,6 +180,10 @@ MENUITEM_SETTING(lcd_sleep_after_backlight_off, #ifdef HAVE_BACKLIGHT_BRIGHTNESS MENUITEM_SETTING(brightness_item, &global_settings.brightness, NULL); #endif +#ifdef HAVE_COMPOSITE_VIDEO_OUT +MENUITEM_SETTING(composite_video_output, + &global_settings.composite_video_output, NULL); +#endif #endif /* HAVE_BACKLIGHT */ #ifdef HAVE_LCD_CONTRAST MENUITEM_SETTING(contrast, &global_settings.contrast, NULL); @@ -214,6 +218,9 @@ MAKE_MENU(lcd_settings,ID2P(LANG_LCD_MENU), ,&brightness_item # endif #endif /* HAVE_BACKLIGHT */ +#ifdef HAVE_COMPOSITE_VIDEO_OUT + ,&composite_video_output +#endif #ifdef HAVE_LCD_CONTRAST ,&contrast #endif @@ -573,6 +580,8 @@ static int line_padding_callback(int action, } MENUITEM_SETTING(touch_mode, &global_settings.touch_mode, touch_mode_callback); +MENUITEM_SETTING(touch_enable_flick_shortcuts, + &global_settings.touch_enable_flick_shortcuts, NULL); MENUITEM_FUNCTION(touchscreen_menu_calibrate, 0, ID2P(LANG_TOUCHSCREEN_CALIBRATE), calibrate, NULL, Icon_NOICON); @@ -581,8 +590,12 @@ MENUITEM_FUNCTION(touchscreen_menu_reset_calibration, 0, reset_mapping, NULL, Icon_NOICON); MENUITEM_SETTING(list_line_padding, &global_settings.list_line_padding, line_padding_callback); -MAKE_MENU(touchscreen_menu, ID2P(LANG_TOUCHSCREEN_SETTINGS), NULL, Icon_NOICON, &list_line_padding, &touch_mode, - &touchscreen_menu_calibrate, &touchscreen_menu_reset_calibration); +MAKE_MENU(touchscreen_menu, ID2P(LANG_TOUCHSCREEN_SETTINGS), NULL, Icon_NOICON, + &touch_mode, + &touch_enable_flick_shortcuts, + &list_line_padding, + &touchscreen_menu_calibrate, + &touchscreen_menu_reset_calibration); #endif static int codepage_callback(int action, diff --git a/apps/menus/eq_menu.c b/apps/menus/eq_menu.c index 267019035c..370f8bcad2 100644 --- a/apps/menus/eq_menu.c +++ b/apps/menus/eq_menu.c @@ -180,6 +180,17 @@ static const struct int_setting cutoff_int_setting = { .get_talk_id = NULL, }; +static const struct choice_setting type_choice_setting = { + .option_callback = NULL, + .count = 3, + .cfg_vals = "low_shelf,peaking,high_shelf", + {.desc = (const unsigned char*[]) { + ID2P(LANG_EQUALIZER_BAND_LOW_SHELF), + ID2P(LANG_EQUALIZER_BAND_PEAK), + ID2P(LANG_EQUALIZER_BAND_HIGH_SHELF), + }} +}; + static int simplelist_action_callback(int action, struct gui_synclist *lists) { (void)lists; @@ -234,7 +245,7 @@ static void selection_to_banditem(int selection, int expanded_band, int *band, i *item = 0; *band = selection; } - else if (diff < 4) + else if (diff < 5) { *item = selection - expanded_band; *band = expanded_band; @@ -242,7 +253,7 @@ static void selection_to_banditem(int selection, int expanded_band, int *band, i else { *item = 0; - *band = expanded_band + diff - 3; + *band = expanded_band + diff - 4; } } @@ -257,23 +268,13 @@ static char *advancedmenu_item_get_name(int selected_item, void *data, char *buf switch (item) { case 0: /* Band title */ - if (band == 0) - return str(LANG_EQUALIZER_BAND_LOW_SHELF); - else if (band == EQ_NUM_BANDS - 1) - return str(LANG_EQUALIZER_BAND_HIGH_SHELF); - else - { - snprintf(buffer, len, str(LANG_EQUALIZER_BAND_PEAK), band); - return buffer; - } - break; + snprintf(buffer, len, str(LANG_EQUALIZER_BAND), band); + return buffer; case 1: /* cutoff */ - if (band == 0) - lang = LANG_EQUALIZER_BAND_CUTOFF; - else if (band == EQ_NUM_BANDS - 1) - lang = LANG_EQUALIZER_BAND_CUTOFF; - else + if (global_settings.eq_band_settings[band].type == EQ_FILTER_PEAK) lang = LANG_EQUALIZER_BAND_CENTER; + else + lang = LANG_EQUALIZER_BAND_CUTOFF; break; case 2: /* Q */ lang = LANG_EQUALIZER_BAND_Q; @@ -281,6 +282,9 @@ static char *advancedmenu_item_get_name(int selected_item, void *data, char *buf case 3: /* Gain */ lang = LANG_GAIN; break; + case 4: /* Type */ + lang = LANG_EQUALIZER_FILTER_TYPE; + break; } if(lang < 0) @@ -305,24 +309,14 @@ static int advancedmenu_speak_item(int selected_item, void *data) switch (item) { case 0: /* Band title */ - if (band == 0) - lang = LANG_EQUALIZER_BAND_LOW_SHELF; - else if (band == EQ_NUM_BANDS - 1) - lang = LANG_EQUALIZER_BAND_HIGH_SHELF; - else - { - talk_id(LANG_EQUALIZER_BAND_PEAK, false); - talk_number(band, true); - return -1; - } - break; + talk_id(LANG_EQUALIZER_BAND, false); + talk_number(band, true); + return -1; case 1: /* cutoff */ - if (band == 0) - lang = LANG_EQUALIZER_BAND_CUTOFF; - else if (band == EQ_NUM_BANDS - 1) - lang = LANG_EQUALIZER_BAND_CUTOFF; - else + if (global_settings.eq_band_settings[band].type == EQ_FILTER_PEAK) lang = LANG_EQUALIZER_BAND_CENTER; + else + lang = LANG_EQUALIZER_BAND_CUTOFF; break; case 2: /* Q */ lang = LANG_EQUALIZER_BAND_Q; @@ -368,7 +362,6 @@ static int eq_do_advanced_menu(void * param) info.action_callback = simplelist_action_callback; info.selection = -1; info.title_icon = Icon_EQ; - setting.flags = F_BANFROMQS|F_INT_SETTING|F_T_INT|F_NO_WRAP; while (true) { @@ -388,7 +381,7 @@ static int eq_do_advanced_menu(void * param) } else { - extra = 3; + extra = 4; selected_band = band; } info.selection = band; @@ -396,7 +389,8 @@ static int eq_do_advanced_menu(void * param) continue; } case 1: /* cutoff */ - if (band == 0 || band == EQ_NUM_BANDS - 1) + setting.flags = F_BANFROMQS|F_INT_SETTING|F_T_INT|F_NO_WRAP; + if (global_settings.eq_band_settings[band].type != EQ_FILTER_PEAK) setting.lang_id = LANG_EQUALIZER_BAND_CUTOFF; else setting.lang_id = LANG_EQUALIZER_BAND_CENTER; @@ -405,17 +399,26 @@ static int eq_do_advanced_menu(void * param) setting.setting = &global_settings.eq_band_settings[band].cutoff; break; case 2: /* Q */ + setting.flags = F_BANFROMQS|F_INT_SETTING|F_T_INT|F_NO_WRAP; setting.lang_id = LANG_EQUALIZER_BAND_Q; setting.default_val.int_ = eq_defaults[band].q; setting.int_setting = &q_int_setting; setting.setting = &global_settings.eq_band_settings[band].q; break; case 3: /* Gain */ + setting.flags = F_BANFROMQS|F_INT_SETTING|F_T_INT|F_NO_WRAP; setting.lang_id = LANG_GAIN; setting.default_val.int_ = eq_defaults[band].gain; setting.int_setting = &gain_int_setting; setting.setting = &global_settings.eq_band_settings[band].gain; break; + case 4: /* Type */ + setting.flags = F_BANFROMQS|F_CHOICE_SETTING|F_T_INT; + setting.lang_id = LANG_EQUALIZER_FILTER_TYPE; + setting.default_val.int_ = eq_defaults[band].type; + setting.choice_setting = &type_choice_setting; + setting.setting = &global_settings.eq_band_settings[band].type; + break; } pcmbuf_set_low_latency(true); advancedmenu_item_get_name(info.selection, &selected_band, title, MAX_PATH); @@ -430,21 +433,17 @@ MENUITEM_FUNCTION(advanced_menu, 0, ID2P(LANG_EQUALIZER_ADVANCED), eq_do_advanced_menu, NULL, Icon_EQ); enum eq_slider_mode { + TYPE, GAIN, CUTOFF, Q, }; -enum eq_type { - LOW_SHELF, - PEAK, - HIGH_SHELF -}; - /* Draw the UI for a whole EQ band */ static int draw_eq_slider(struct screen * screen, int x, int y, int width, int cutoff, int q, int gain, bool selected, - enum eq_slider_mode mode, int band, int scrollbar_size) + enum eq_slider_mode mode, int band, int scrollbar_size, + enum eq_filter_type type) { char buf[26]; int steps, min_item, max_item; @@ -453,6 +452,11 @@ static int draw_eq_slider(struct screen * screen, int x, int y, int w, h; switch(mode) { + case TYPE: + steps = 5; + min_item = (type * 2); + max_item = (type * 2) + 1; + break; case Q: steps = EQ_Q_MAX - EQ_Q_MIN; min_item = q - EQ_Q_STEP - EQ_Q_MIN; @@ -476,11 +480,17 @@ static int draw_eq_slider(struct screen * screen, int x, int y, y1 = y + 2; /* Print out the band label */ - if (band == 0) { - screen->putsxy(x1, y1, "LS: "); + if (mode == TYPE && selected) + screen->set_drawmode(DRMODE_SOLID | DRMODE_INVERSEVID); + else + screen->set_drawmode(DRMODE_SOLID); + if (type == EQ_FILTER_LOW_SHELF) { + snprintf(buf, sizeof(buf), "LS%d:", band); + screen->putsxy(x1, y1, buf); /*screen->getstringsize("LS:", &w, &h); UNUSED*/ - } else if (band == EQ_NUM_BANDS - 1) { - screen->putsxy(x1, y1, "HS: "); + } else if (type == EQ_FILTER_HIGH_SHELF) { + snprintf(buf, sizeof(buf), "HS%d:", band); + screen->putsxy(x1, y1, buf); /*screen->getstringsize("HS:", &w, &h); UNUSED*/ } else { snprintf(buf, sizeof(buf), "PK%d:", band); @@ -557,6 +567,7 @@ static void draw_eq_sliders(struct screen * screen, int x, int y, int cutoff = setting->cutoff; int q = setting->q; int gain = setting->gain; + enum eq_filter_type type = setting->type; if (i == start_item + nb_eq_sliders) break; @@ -564,7 +575,7 @@ static void draw_eq_sliders(struct screen * screen, int x, int y, if (i >= start_item) { height += draw_eq_slider(screen, x, height, screen->lcdwidth - x - 1, cutoff, q, gain, i == current_band, mode, - i, scrollbar_size); + i, scrollbar_size, type); /* add a margin */ height++; } @@ -629,7 +640,18 @@ int eq_menu_graphical(void) screens[i].clear_display(); /* Set pointer to the band data currently editable */ - if (mode == GAIN) { + if (mode == TYPE) { + /* type */ + setting = (int*) &global_settings.eq_band_settings[current_band].type; + + step = 1; + fast_step = 1; + min = EQ_FILTER_LOW_SHELF; + max = EQ_FILTER_HIGH_SHELF; + + screens[i].putsxyf(0, 0, str(LANG_SYSFONT_EQUALIZER_EDIT_MODE), + str(LANG_EQUALIZER_FILTER_TYPE), ""); + } else if (mode == GAIN) { /* gain */ setting = &global_settings.eq_band_settings[current_band].gain; @@ -734,7 +756,7 @@ int eq_menu_graphical(void) case ACTION_STD_OK: mode++; if (mode > Q) - mode = GAIN; /* wrap around */ + mode = TYPE; /* wrap around */ break; case ACTION_STD_CANCEL: @@ -785,6 +807,7 @@ static struct browse_folder_info eqs = { EQS_DIR, SHOW_CFG }; static void eq_reset_defaults(void) { for (int i = 0; i < EQ_NUM_BANDS; i++) { + global_settings.eq_band_settings[i].type = eq_defaults[i].type; global_settings.eq_band_settings[i].cutoff = eq_defaults[i].cutoff; global_settings.eq_band_settings[i].q = eq_defaults[i].q; global_settings.eq_band_settings[i].gain = eq_defaults[i].gain; diff --git a/apps/menus/settings_menu.c b/apps/menus/settings_menu.c index 9b017dc2f9..1879ada8fe 100644 --- a/apps/menus/settings_menu.c +++ b/apps/menus/settings_menu.c @@ -368,7 +368,7 @@ MENUITEM_SETTING(lineout_onoff, &global_settings.lineout_active, NULL); MENUITEM_SETTING(usb_hid, &global_settings.usb_hid, NULL); MENUITEM_SETTING(usb_keypad_mode, &global_settings.usb_keypad_mode, NULL); #endif -#ifdef USB_ENABLE_AUDIO +#if defined(USB_ENABLE_AUDIO) || defined(HAVE_HOST_USB_AUDIO) MENUITEM_SETTING(usb_audio, &global_settings.usb_audio, NULL); #endif #if defined(USB_ENABLE_STORAGE) && defined(HAVE_MULTIDRIVE) @@ -421,7 +421,7 @@ MAKE_MENU(usb_menu, ID2P(LANG_USB_MENU), 0, Icon_NOICON, &usb_hid, &usb_keypad_mode, #endif -#ifdef USB_ENABLE_AUDIO +#if defined(USB_ENABLE_AUDIO) || defined(HAVE_HOST_USB_AUDIO) &usb_audio, #endif #if defined(USB_ENABLE_STORAGE) && defined(HAVE_MULTIDRIVE) @@ -480,7 +480,7 @@ MAKE_MENU(system_menu, ID2P(LANG_SYSTEM), &sel_softlock, #endif #if defined(USB_ENABLE_HID) || \ - defined(USB_ENABLE_AUDIO) || \ + defined(USB_ENABLE_AUDIO) || defined(HAVE_HOST_USB_AUDIO) || \ (defined(USB_ENABLE_STORAGE) && defined(HAVE_MULTIDRIVE)) || \ defined(HAVE_USB_MODE) &usb_menu, diff --git a/apps/misc.c b/apps/misc.c index ea4726d0c2..af9397ad2a 100644 --- a/apps/misc.c +++ b/apps/misc.c @@ -297,6 +297,8 @@ int fast_readline(int fd, char *buf, int buf_size, void *parameters, return 0; } +#endif /* !defined(__PCTOOL__) */ + /* parse a line from a configuration file. the line format is: name: value @@ -338,6 +340,8 @@ bool settings_parseline(char* line, char** name, char** value) return true; } +#ifndef __PCTOOL__ + static void system_flush(void) { playlist_shutdown(); @@ -809,6 +813,14 @@ long default_event_handler_ex(long event, void (*callback)(void *), void *parame case BUTTON_MULTIMEDIA_STOP: list_stop_handler(); return event; + case BUTTON_MULTIMEDIA_VOLUME_UP: + case BUTTON_MULTIMEDIA_VOLUME_UP|BUTTON_REPEAT: + adjust_volume(1); + return event; + case BUTTON_MULTIMEDIA_VOLUME_DOWN: + case BUTTON_MULTIMEDIA_VOLUME_DOWN|BUTTON_REPEAT: + adjust_volume(-1); + return event; case BUTTON_MULTIMEDIA_REW: case BUTTON_MULTIMEDIA_FFWD: /* not supported yet, needs to be done in the WPS */ diff --git a/apps/playback.c b/apps/playback.c index 719802e030..d69b28cd2c 100644 --- a/apps/playback.c +++ b/apps/playback.c @@ -27,6 +27,7 @@ #include "core_alloc.h" #include "sound.h" #include "pcm_sink.h" +#include "pcm-internal.h" #include "codecs.h" #include "codec_thread.h" #include "voice_thread.h" @@ -66,8 +67,11 @@ #include /* For strncasecmp() */ #endif +#if defined(USB_ENABLE_AUDIO) || defined(HAVE_HOST_USB_AUDIO) #ifdef USB_ENABLE_AUDIO #include "usbstack/usb_audio.h" +#endif +#include "usb.h" #include "splash.h" #include "lang.h" #endif @@ -1254,7 +1258,7 @@ void allocate_playback_log(void) playback_log_handle = core_alloc(PLAYBACK_LOG_BUFSZ); if (playback_log_handle > 0) { - DEBUGF("%s Allocated %d bytes\n", __func__, PLAYBACK_LOG_BUFSZ); + DEBUGF("%s Allocated %d bytes\n", __func__, PLAYBACK_LOG_BUFSZ); char *buf = core_get_data(playback_log_handle); buf[0] = '\0'; } @@ -2682,6 +2686,9 @@ static bool single_mode_do_pause(int id3_hid) if (global_settings.single_mode == SINGLE_MODE_TRACK) return true; + if (global_settings.single_mode == SINGLE_MODE_PLAYLIST) + return (skip_pending == TRACK_SKIP_AUTO_NEW_PLAYLIST); + char *previous_tag = single_mode_get_id3_tag(id3_get(PLAYING_ID3)); char *new_tag = single_mode_get_id3_tag(bufgetid3(id3_hid)); return previous_tag == NULL || @@ -2988,12 +2995,12 @@ static void audio_on_track_changed(void) static void audio_start_playback(const struct audio_resume_info *resume_info, unsigned int flags) { -/* +/* * Refuse to start playback if usb audio is active. See gui_wps_show() for * a splash message to the user. * NOTE: if USBAudio ever gets its own DSP channel, this block can go away! */ -#ifdef USB_ENABLE_AUDIO +#if defined(USB_ENABLE_AUDIO) || defined(HAVE_HOST_USB_AUDIO) if (usb_audio_get_active()) { queue_reply(&audio_queue, 0); @@ -3033,12 +3040,18 @@ static void audio_start_playback(const struct audio_resume_info *resume_info, track_event_flags = TEF_NONE; ff_rw_mode = false; + /* + * When restarting playback or resuming from a paused state, + * the PCM buffer should be cleared to ensure crossfade will + * not be performed. + */ + if ((flags & AUDIO_START_RESTART) || old_status == PLAY_PAUSED) + pcmbuf_play_stop(); + if (flags & AUDIO_START_RESTART) { /* Clear out some stuff to resume the current track where it left off */ - pcmbuf_play_stop(); - resume.elapsed = id3_get(PLAYING_ID3)->elapsed; resume.offset = id3_get(PLAYING_ID3)->offset; skip_resume_adjustments = id3_get(PLAYING_ID3)->skip_resume_adjustments; @@ -3984,8 +3997,11 @@ void audio_hard_stop(void) LOGFQUEUE("audio >| audio Q_AUDIO_STOP: 1"); audio_queue_send(Q_AUDIO_STOP, 1); #ifdef PLAYBACK_VOICE + /* Stop voice */ voice_stop(); #endif + /* Stop PCM */ + pcm_play_stop(); audiobuf_handle = core_free(audiobuf_handle); } @@ -4254,7 +4270,8 @@ static unsigned long audio_guess_frequency(struct mp3entry *id3) have_44 = true; if (caps->samprs[i] == SAMPR_48) have_48 = true; - if (id3->frequency == caps->samprs[i]) + /* Always upscale to 44.1 or better */ + if (id3->frequency >= SAMPR_44 && id3->frequency == caps->samprs[i]) return id3->frequency; } unsigned long fallback = (id3->frequency % 4000) ? SAMPR_44 : SAMPR_48; diff --git a/apps/playlist.c b/apps/playlist.c index 9140d25a8c..294e2093b3 100644 --- a/apps/playlist.c +++ b/apps/playlist.c @@ -3218,7 +3218,7 @@ int playlist_resume(void) } playlist->control_created = true; - control_file_size = filesize(playlist->control_fd); + control_file_size = ffilesize(playlist->control_fd); if (control_file_size <= 0) { notify_control_access_error(); diff --git a/apps/playlist_catalog.c b/apps/playlist_catalog.c index 7a7e082e21..eee746d0f9 100644 --- a/apps/playlist_catalog.c +++ b/apps/playlist_catalog.c @@ -325,7 +325,7 @@ int catalog_insert_into(const char* playlist, bool new_playlist, goto exit; i = lseek(f, 0, SEEK_CUR); - fs = filesize(f); + fs = ffilesize(f); while (i < fs) { int n; diff --git a/apps/playlist_viewer.c b/apps/playlist_viewer.c index 639007ec10..e35700732c 100644 --- a/apps/playlist_viewer.c +++ b/apps/playlist_viewer.c @@ -1151,16 +1151,12 @@ enum playlist_viewer_result playlist_viewer_ex(const char* filename, goto exit; #ifdef HAVE_QUICKSCREEN case ACTION_STD_QUICKSCREEN: + /* Shortcuts menu currently disabled in Playlist Viewer */ if (!global_settings.shortcuts_replaces_qs) { - if (quick_screen_quick(button) == - QUICKSCREEN_GOTO_SHORTCUTS_MENU) /* currently disabled */ - { - /* QuickScreen defers skin updates when popping its activity - to switch to Shortcuts Menu, so make up for that here: */ - FOR_NB_SCREENS(i) - skin_update(CUSTOM_STATUSBAR, i, SKIN_REFRESH_ALL); - } + /* QUICKSCREEN_GOTO_SHORTCUTS_MENU + return value ignored for now */ + quickscreen_show(button); update_playlist(true); update_gui(&playlist_lists, true); } diff --git a/apps/plugin.c b/apps/plugin.c index d06e9936e3..dfb1cbdb82 100644 --- a/apps/plugin.c +++ b/apps/plugin.c @@ -67,6 +67,10 @@ #include "usbstack/usb_audio.h" #endif +#if defined(IRIVER_H100_SERIES) || defined(IRIVER_H300_SERIES) +#include "system-iriver.h" +#endif + #define WRAPPER(_x_) _x_ ## _wrapper #if (CONFIG_PLATFORM & PLATFORM_HOSTED) @@ -263,6 +267,7 @@ static const struct plugin_api rockbox_api = { font_load, font_unload, font_get, + font_measurestring, font_getstringsize, font_get_width, screen_clear_area, @@ -319,6 +324,7 @@ static const struct plugin_api rockbox_api = { is_backlight_on, backlight_on, backlight_off, + backlight_set_on_button_hold, backlight_set_timeout, #ifdef HAVE_BACKLIGHT_BRIGHTNESS backlight_set_brightness, @@ -335,6 +341,9 @@ static const struct plugin_api rockbox_api = { #if CONFIG_CHARGING remote_backlight_set_timeout_plugged, #endif +#if defined(HAS_REMOTE_BUTTON_HOLD) + remote_backlight_set_on_button_hold, +#endif #endif /* HAVE_REMOTE_LCD */ #endif /* HAVE_BACKLIGHT */ @@ -381,6 +390,8 @@ static const struct plugin_api rockbox_api = { gesture_vel_reset, gesture_vel_process, gesture_vel_get, + gesture_flick_get_in_vp, + gesture_flick_get, #endif action_userabort, core_set_keyremap, @@ -427,7 +438,7 @@ static const struct plugin_api rockbox_api = { FS_PREFIX(remove), FS_PREFIX(rename), FS_PREFIX(ftruncate), - FS_PREFIX(filesize), + FS_PREFIX(ffilesize), fdprintf, read_line, settings_parseline, @@ -457,7 +468,7 @@ static const struct plugin_api rockbox_api = { FS_PREFIX(mkdir), FS_PREFIX(rmdir), FS_PREFIX(dir_exists), - dir_get_info, + FS_PREFIX(dir_get_info), /* browsing */ rockbox_browse, @@ -517,7 +528,9 @@ static const struct plugin_api rockbox_api = { set_sleeptimer_duration, /*stub*/ get_sleep_timer, /*stub*/ #if (CONFIG_PLATFORM & PLATFORM_NATIVE) +#if defined(CPU_COLDFIRE) system_memory_guard, +#endif &cpu_frequency, #ifdef HAVE_ADJUSTABLE_CPU_FREQ @@ -847,7 +860,7 @@ static const struct plugin_api rockbox_api = { wheel_send_events, #endif -#if defined(IRIVER_H100_SERIES) || defined(IRIVER_H300_SERIES) +#if !defined(SIMULATOR) && (defined(IRIVER_H100_SERIES) || defined(IRIVER_H300_SERIES)) /* Routines for the iriver_flash -plugin. */ detect_original_firmware, detect_flashed_ramimage, @@ -873,9 +886,12 @@ static const struct plugin_api rockbox_api = { path_strip_volume, #endif +#ifdef HAVE_HW_H264 + &target_hw_h264_api, +#endif + /* new stuff at the end, sort into place next time the API gets incompatible */ - font_measurestring, }; static int plugin_buffer_handle; diff --git a/apps/plugin.h b/apps/plugin.h index 7ccc4b1239..26432b5b9b 100644 --- a/apps/plugin.h +++ b/apps/plugin.h @@ -83,6 +83,7 @@ int plugin_open(const char *plugin, const char *parameter); #include "metadata.h" #include "sound.h" #include "audio.h" +#include "hw_h264.h" #include "voice_thread.h" #include "root_menu.h" #include "talk.h" @@ -179,7 +180,7 @@ int plugin_open(const char *plugin, const char *parameter); * when this happens please take the opportunity to sort in * any new functions "waiting" at the end of the list. */ -#define PLUGIN_API_VERSION 284 +#define PLUGIN_API_VERSION 285 /* 239 Marks the removal of ARCHOS HWCODEC and CHARCELL */ @@ -306,6 +307,8 @@ struct plugin_api { int (*font_load)(const char *path); void (*font_unload)(int font_id); struct font* (*font_get)(int font); + int (*font_measurestring)(const unsigned char *str, size_t maxbytes, + size_t maxwidth, int *w, int *h, int fontnum); int (*font_getstringsize)(const unsigned char *str, int *w, int *h, int fontnumber); int (*font_get_width)(struct font* pf, ucschar_t char_code); @@ -373,11 +376,11 @@ struct plugin_api { bool (*is_backlight_on)(bool ignore_always_off); void (*backlight_on)(void); void (*backlight_off)(void); + void (*backlight_set_on_button_hold)(int index); void (*backlight_set_timeout)(int index); #ifdef HAVE_BACKLIGHT_BRIGHTNESS void (*backlight_set_brightness)(int val); #endif /* HAVE_BACKLIGHT_BRIGHTNESS */ - #if CONFIG_CHARGING void (*backlight_set_timeout_plugged)(int index); #endif @@ -389,6 +392,9 @@ struct plugin_api { #if CONFIG_CHARGING void (*remote_backlight_set_timeout_plugged)(int index); #endif +#if defined(HAS_REMOTE_BUTTON_HOLD) + void (*remote_backlight_set_on_button_hold)(int index); +#endif #endif /* HAVE_REMOTE_LCD */ #endif /* HAVE_BACKLIGHT */ @@ -450,6 +456,9 @@ struct plugin_api { void (*gesture_vel_process)(struct gesture_vel *gv, const struct touchevent *ev); bool (*gesture_vel_get)(struct gesture_vel *gv, int *xvel, int *yvel); + int (*gesture_flick_get_in_vp)(const struct gesture_event *gevt, + const struct viewport *vp); + int (*gesture_flick_get)(const struct gesture_event *gevt); #endif bool (*action_userabort)(int timeout); int (*core_set_keyremap)(struct button_mapping* core_keymap, int count); @@ -495,7 +504,7 @@ struct plugin_api { int (*remove)(const char *path); int (*rename)(const char *old, const char *new); int (*ftruncate)(int fildes, off_t length); - off_t (*filesize)(int fildes); + off_t (*ffilesize)(int fildes); int (*fdprintf)(int fildes, const char *fmt, ...) ATTRIBUTE_PRINTF(2, 3); int (*read_line)(int fd, char* buffer, int buffer_size); bool (*settings_parseline)(char* line, char** name, char** value); @@ -602,7 +611,9 @@ struct plugin_api { void (*set_sleeptimer_duration)(int minutes); int (*get_sleep_timer)(void); #if (CONFIG_PLATFORM & PLATFORM_NATIVE) +#if defined(CPU_COLDFIRE) int (*system_memory_guard)(int newmode); +#endif long *cpu_frequency; #ifdef HAVE_ADJUSTABLE_CPU_FREQ #ifdef CPU_BOOST_LOGGING @@ -1014,7 +1025,7 @@ struct plugin_api { void (*wheel_send_events)(bool send); #endif -#if defined(IRIVER_H100_SERIES) || defined(IRIVER_H300_SERIES) +#if !defined(SIMULATOR) && (defined(IRIVER_H100_SERIES) || defined(IRIVER_H300_SERIES)) /* Routines for the iriver_flash -plugin. */ bool (*detect_original_firmware)(void); bool (*detect_flashed_ramimage)(void); @@ -1037,10 +1048,12 @@ struct plugin_api { int (*path_strip_volume)(const char *name, const char **nameptr, bool greedy); #endif +#ifdef HAVE_HW_H264 + const struct hw_h264_api *hw_h264; +#endif + /* new stuff at the end, sort into place next time the API gets incompatible */ - int (*font_measurestring)(const unsigned char *str, size_t maxbytes, - size_t maxwidth, int *w, int *h, int fontnum); }; /* plugin header */ diff --git a/apps/plugins/CATEGORIES b/apps/plugins/CATEGORIES index bf646a5bf9..245be6cc15 100644 --- a/apps/plugins/CATEGORIES +++ b/apps/plugins/CATEGORIES @@ -179,6 +179,7 @@ sudoku,games test_boost,apps test_mem,apps test_codec,viewers +test_cyc,apps test_disk,apps test_fps,apps test_grey,apps diff --git a/apps/plugins/SOURCES b/apps/plugins/SOURCES index 797601e4dd..8e11baa23b 100644 --- a/apps/plugins/SOURCES +++ b/apps/plugins/SOURCES @@ -217,6 +217,11 @@ test_codec.c #ifdef HAVE_JPEG test_core_jpeg.c #endif +#if (CONFIG_PLATFORM & PLATFORM_NATIVE) && defined(CPU_ARM) \ + && !defined(CPU_ARM_MICRO) \ + && PLUGIN_BUFFER_SIZE > 0x20000 /* about 140 KB on ARMv5 */ +test_cyc.c +#endif test_disk.c test_fps.c test_gfx.c diff --git a/apps/plugins/SOURCES.app_build b/apps/plugins/SOURCES.app_build index cdd764dc27..439a6dd29c 100644 --- a/apps/plugins/SOURCES.app_build +++ b/apps/plugins/SOURCES.app_build @@ -1,4 +1,4 @@ -#if !defined(HAVE_TOUCHSCREEN) || defined(DX50) || defined(DX90) || defined(HIBY_R3PROII) || defined(HIBY_R1) +#if !defined(HAVE_TOUCHSCREEN) || defined(DX50) || defined(DX90) || defined(HIBY_R3PROII) || defined(HIBY_R1) || defined(HIDIZS_AP80MAX) /* In devices running RockBox as an application, but having a keypad */ #include "SOURCES" #else diff --git a/apps/plugins/SUBDIRS b/apps/plugins/SUBDIRS index 1ba38c97bb..7c88b086be 100644 --- a/apps/plugins/SUBDIRS +++ b/apps/plugins/SUBDIRS @@ -74,7 +74,7 @@ mikmod (CONFIG_KEYPAD == SANSA_FUZE_PAD) || (CONFIG_KEYPAD == SANSA_E200_PAD) || \ (CONFIG_KEYPAD == IPOD_4G_PAD) || (CONFIG_KEYPAD == IPOD_3G_PAD) || \ (CONFIG_KEYPAD == IPOD_1G2G_PAD || CONFIG_KEYPAD == SAMSUNG_YPR0_PAD) || \ - (CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD) || (CONFIG_KEYPAD == CTRU_PAD) || (CONFIG_KEYPAD == EROSQ_PAD) + (CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD) || (CONFIG_KEYPAD == N3DS_PAD) || (CONFIG_KEYPAD == EROSQ_PAD) /* PDBox is confirmed to run on these player models. */ pdbox #endif @@ -83,6 +83,10 @@ pdbox mpegplayer #endif +#ifdef HAVE_HW_H264 +h264_player +#endif + /* Lua needs at least 160 KB to work in */ #if PLUGIN_BUFFER_SIZE >= 0x80000 lua diff --git a/apps/plugins/SUBDIRS.app_build b/apps/plugins/SUBDIRS.app_build index 5618f41afe..d4c382050c 100644 --- a/apps/plugins/SUBDIRS.app_build +++ b/apps/plugins/SUBDIRS.app_build @@ -1,4 +1,4 @@ -#if !defined(HAVE_TOUCHSCREEN) || defined(DX50) || defined(DX90) || defined(HIBY_R3PROII) || defined(HIBY_R1) +#if !defined(HAVE_TOUCHSCREEN) || defined(DX50) || defined(DX90) || defined(HIBY_R3PROII) || defined(HIBY_R1) || defined(HIDIZS_AP80MAX) /* This is for devices having a keypad, running RockBox as an application */ #include "SUBDIRS" diff --git a/apps/plugins/battery_bench.c b/apps/plugins/battery_bench.c index 284043c14f..5a129c6136 100644 --- a/apps/plugins/battery_bench.c +++ b/apps/plugins/battery_bench.c @@ -243,7 +243,7 @@ struct battery_tables_t { #define BATTERY_ON_TXT "Play" #define BATTERY_OFF_TXT "Power" -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD /* use touchscreen */ #elif CONFIG_KEYPAD == MA_PAD @@ -258,12 +258,18 @@ struct battery_tables_t { #define BATTERY_ON_TXT "A" #define BATTERY_OFF_TXT "X" -#elif CONFIG_KEYPAD == CTRU_PAD +#elif CONFIG_KEYPAD == N3DS_PAD #define BATTERY_ON BUTTON_SELECT #define BATTERY_OFF BUTTON_BACK #define BATTERY_ON_TXT "A - start" #define BATTERY_OFF_TXT "B" +#elif CONFIG_KEYPAD == ECHO_R1_PAD +#define BATTERY_ON BUTTON_START +#define BATTERY_OFF BUTTON_SELECT +#define BATTERY_ON_TXT "Start" +#define BATTERY_OFF_TXT "Select" + #else #error "No keymap defined!" #endif diff --git a/apps/plugins/bench_mem_jpeg.c b/apps/plugins/bench_mem_jpeg.c index db210613b8..25db2d1923 100644 --- a/apps/plugins/bench_mem_jpeg.c +++ b/apps/plugins/bench_mem_jpeg.c @@ -89,7 +89,7 @@ enum plugin_status plugin_start(const void* parameter) lcd_printf("file open failed: %d", fd); goto wait; } - unsigned long filesize = rb->filesize(fd); + unsigned long filesize = rb->ffilesize(fd); if (filesize > plugin_buf_len) { lcd_printf("file too large"); diff --git a/apps/plugins/bitmaps/mono/SOURCES b/apps/plugins/bitmaps/mono/SOURCES index 067942a70e..82f42ec3b1 100644 --- a/apps/plugins/bitmaps/mono/SOURCES +++ b/apps/plugins/bitmaps/mono/SOURCES @@ -20,8 +20,10 @@ bubbles_bubble.138x110x1.bmp bubbles_bubble.220x176x1.bmp #elif ((LCD_WIDTH == 240) && (LCD_HEIGHT == 240)) || \ ((LCD_WIDTH == 320) && (LCD_HEIGHT == 240)) || \ + ((LCD_WIDTH == 400) && (LCD_HEIGHT == 240)) || \ ((LCD_WIDTH == 240) && (LCD_HEIGHT >= 320)) || \ - ((LCD_WIDTH == 360) && (LCD_HEIGHT == 400)) + ((LCD_WIDTH == 360) && (LCD_HEIGHT == 400)) || \ + ((LCD_WIDTH == 360) && (LCD_HEIGHT == 640)) bubbles_bubble.320x240x1.bmp #elif ((LCD_WIDTH == 640) && (LCD_HEIGHT == 480)) || \ ((LCD_WIDTH == 480) && (LCD_HEIGHT >= 640)) diff --git a/apps/plugins/bitmaps/native/SOURCES b/apps/plugins/bitmaps/native/SOURCES index b2286bcc33..da32862caf 100644 --- a/apps/plugins/bitmaps/native/SOURCES +++ b/apps/plugins/bitmaps/native/SOURCES @@ -438,9 +438,9 @@ invadrox_ufo_explode.21x8x16.bmp invadrox_numbers.50x7x16.bmp #if LCD_WIDTH == 480 invadrox_background.480x640x16.bmp -#elif LCD_WIDTH == 360 && LCD_HEIGHT == 400 +#elif LCD_WIDTH == 360 && (LCD_HEIGHT == 400 || LCD_HEIGHT == 640) invadrox_background.360x400x16.bmp -#elif LCD_WIDTH == 320 +#elif LCD_WIDTH == 320 || (LCD_WIDTH == 400 && LCD_HEIGHT == 240) invadrox_background.320x240x16.bmp #elif LCD_WIDTH == 240 && LCD_HEIGHT == 240 invadrox_background.240x240x16.bmp @@ -511,9 +511,9 @@ jewels.220x176x16.bmp jewels.240x240x16.bmp #elif (LCD_WIDTH == 240) && (LCD_HEIGHT >= 320) jewels.320x240x16.bmp -#elif (LCD_WIDTH == 320) && (LCD_HEIGHT == 240) +#elif ((LCD_WIDTH == 320) || (LCD_WIDTH == 400)) && (LCD_HEIGHT == 240) jewels.320x240x16.bmp -#elif (LCD_WIDTH == 360) && (LCD_HEIGHT == 400) +#elif (LCD_WIDTH == 360) && ((LCD_HEIGHT == 400) || (LCD_HEIGHT == 640)) jewels.360x400x16.bmp #elif ((LCD_WIDTH >= 480) && (LCD_HEIGHT >= 640)) || \ ((LCD_WIDTH >= 640) && (LCD_HEIGHT >= 480)) @@ -909,7 +909,8 @@ sudoku_inverse.240x240x16.bmp sudoku_start.320x240x16.bmp sudoku_normal.320x240x16.bmp sudoku_inverse.320x240x16.bmp -#elif (LCD_WIDTH == 320) && (LCD_HEIGHT == 240) && (LCD_DEPTH >= 16) +#elif ((LCD_WIDTH == 320) || (LCD_WIDTH == 400)) && (LCD_HEIGHT == 240) \ + && (LCD_DEPTH >= 16) sudoku_start.320x240x16.bmp sudoku_normal.320x240x16.bmp sudoku_inverse.320x240x16.bmp @@ -943,11 +944,13 @@ superdom_boarditems.220x176x16.bmp ((LCD_WIDTH == 176) && (LCD_HEIGHT == 220)) superdom_boarditems.176x132x16.bmp #elif ((LCD_WIDTH == 240) && (LCD_HEIGHT == 240)) || \ - ((LCD_WIDTH == 320) && (LCD_HEIGHT == 240)) + ((LCD_WIDTH == 320) && (LCD_HEIGHT == 240)) || \ + ((LCD_WIDTH == 400) && (LCD_HEIGHT == 240)) superdom_boarditems.320x240x16.bmp #elif ((LCD_WIDTH == 240) && (LCD_HEIGHT == 320)) || \ ((LCD_WIDTH == 240) && (LCD_HEIGHT == 400)) || \ - ((LCD_WIDTH == 360) && (LCD_HEIGHT == 400)) + ((LCD_WIDTH == 360) && (LCD_HEIGHT == 400)) || \ + ((LCD_WIDTH == 360) && (LCD_HEIGHT == 640)) superdom_boarditems.240x320x16.bmp #elif (LCD_WIDTH >= 480 && LCD_HEIGHT >= 640) superdom_boarditems.480x640x16.bmp diff --git a/apps/plugins/blackjack.c b/apps/plugins/blackjack.c index d5a84b7e4e..d90fed287f 100644 --- a/apps/plugins/blackjack.c +++ b/apps/plugins/blackjack.c @@ -591,7 +591,7 @@ enum { #define BJACK_LEFT BUTTON_LEFT -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD #define BJACK_QUIT BUTTON_POWER #define BJACK_QUIT_NAME "QUIT" @@ -611,7 +611,7 @@ enum { #define BJACK_RIGHT BUTTON_RIGHT #define BJACK_LEFT BUTTON_LEFT -#elif CONFIG_KEYPAD == CTRU_PAD +#elif CONFIG_KEYPAD == N3DS_PAD #define BJACK_SELECT_NAME "A" #define BJACK_STAY_NAME "X" #define BJACK_QUIT_NAME "B" @@ -627,6 +627,22 @@ enum { #define BJACK_RIGHT BUTTON_RIGHT #define BJACK_LEFT BUTTON_LEFT +#elif CONFIG_KEYPAD == ECHO_R1_PAD +#define BJACK_SELECT_NAME "A" +#define BJACK_STAY_NAME "X" +#define BJACK_QUIT_NAME "B" +#define BJACK_DOUBLE_NAME "Y" +#define BJACK_SELECT BUTTON_A +#define BJACK_QUIT BUTTON_POWER +#define BJACK_MAX BUTTON_B +#define BJACK_MIN BUTTON_Y +#define BJACK_STAY BUTTON_X +#define BJACK_DOUBLEDOWN BUTTON_START +#define BJACK_UP BUTTON_UP +#define BJACK_DOWN BUTTON_DOWN +#define BJACK_RIGHT BUTTON_RIGHT +#define BJACK_LEFT BUTTON_LEFT + #else #error No keymap defined! #endif diff --git a/apps/plugins/brickmania.c b/apps/plugins/brickmania.c index 855aeb98db..08a6e36a3e 100644 --- a/apps/plugins/brickmania.c +++ b/apps/plugins/brickmania.c @@ -353,7 +353,7 @@ CONFIG_KEYPAD == SANSA_M200_PAD #define UP BUTTON_UP #define DOWN BUTTON_DOWN -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD #define QUIT BUTTON_POWER #elif CONFIG_KEYPAD == MA_PAD @@ -373,7 +373,7 @@ CONFIG_KEYPAD == SANSA_M200_PAD #define UP BUTTON_UP #define DOWN BUTTON_DOWN -#elif CONFIG_KEYPAD == CTRU_PAD +#elif CONFIG_KEYPAD == N3DS_PAD #define QUIT BUTTON_BACK #define LEFT BUTTON_LEFT #define RIGHT BUTTON_RIGHT @@ -381,6 +381,14 @@ CONFIG_KEYPAD == SANSA_M200_PAD #define UP BUTTON_UP #define DOWN BUTTON_DOWN +#elif CONFIG_KEYPAD == ECHO_R1_PAD +#define QUIT BUTTON_POWER +#define LEFT BUTTON_LEFT +#define RIGHT BUTTON_RIGHT +#define SELECT BUTTON_A +#define UP BUTTON_UP +#define DOWN BUTTON_DOWN + #else #error No keymap defined! #endif diff --git a/apps/plugins/bubbles.c b/apps/plugins/bubbles.c index a647d98fad..5774fa089a 100644 --- a/apps/plugins/bubbles.c +++ b/apps/plugins/bubbles.c @@ -138,6 +138,17 @@ enum { #define XOFS 128 #define MAX_FPS 40 +#elif (LCD_WIDTH == 360) && (LCD_HEIGHT == 640) +#define XOFS 86 +#define YOFS 220 +#define SCORE_TXT_X 28 +#define SCORE_TXT_WIDTH 32 +#define SCORE_TXT_Y 48 +#define LEVEL_TXT_X 27 +#define LEVEL_TXT_WIDTH 32 +#define LEVEL_TXT_Y 5 +#define MAX_FPS 40 + #elif (LCD_WIDTH == 360) && (LCD_HEIGHT == 400) #define XOFS 86 #define YOFS 100 @@ -159,6 +170,23 @@ enum { #define NEXT_BB_WIDTH 32 #define NEXT_BB_Y 200 +/* 22x22 bubbles: the 320x240 layout, centred (Samsung YP-CP3) */ +#elif (LCD_HEIGHT == 240) && (LCD_WIDTH == 400) +#define LAYOUT_X ((LCD_WIDTH - 320) / 2) +#define XOFS (LAYOUT_X + 72) +#define MAX_FPS 40 + +/* the 320x240 text positions, which centre on its left panel */ +#define LEVEL_TXT_X LAYOUT_X +#define LEVEL_TXT_WIDTH 72 +#define LEVEL_TXT_Y 2 +#define SCORE_TXT_X LAYOUT_X +#define SCORE_TXT_WIDTH 72 +#define SCORE_TXT_Y 29 +#define NEXT_BB_X (LAYOUT_X + 20) +#define NEXT_BB_WIDTH 32 +#define NEXT_BB_Y 200 + /* 22x22 bubbles (Gigabeat, Onda VX747) */ #elif ((LCD_HEIGHT == 320) || (LCD_HEIGHT == 400)) && (LCD_WIDTH == 240) #define XOFS 64 @@ -1513,7 +1541,9 @@ static void bubbles_drawboard(struct game_context* bb) { /* clear screen */ rb->lcd_clear_display(); -#if ((LCD_HEIGHT >= 128) && (LCD_WIDTH <= 320)) || ((LCD_WIDTH == 132) && (LCD_HEIGHT == 80)) +#if ((LCD_HEIGHT >= 128) && (LCD_WIDTH <= 320)) || \ + ((LCD_WIDTH == 132) && (LCD_HEIGHT == 80)) || \ + ((LCD_WIDTH == 400) && (LCD_HEIGHT == 240)) h = rb->font_get(FONT_SYSFIXED)->height + 1; #elif (LCD_WIDTH == 640) && (LCD_HEIGHT == 480) h = rb->font_get(FONT_SYSFIXED)->height + 6; @@ -1523,7 +1553,13 @@ static void bubbles_drawboard(struct game_context* bb) { #endif /* draw background */ #ifdef HAVE_LCD_COLOR +#ifdef LAYOUT_X + /* the layout's background, centred */ + rb->lcd_bitmap(bubbles_background, LAYOUT_X, 0, + LCD_WIDTH - 2 * LAYOUT_X, LCD_HEIGHT); +#else rb->lcd_bitmap(bubbles_background, 0, 0, LCD_WIDTH, LCD_HEIGHT); +#endif #endif /* display play board */ diff --git a/apps/plugins/calculator.c b/apps/plugins/calculator.c index 2feee39799..a42564dd2a 100644 --- a/apps/plugins/calculator.c +++ b/apps/plugins/calculator.c @@ -522,7 +522,7 @@ F3: equal to "=" #define CALCULATOR_CALC BUTTON_MENU #define CALCULATOR_CLEAR BUTTON_BACK -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD #define CALCULATOR_QUIT BUTTON_POWER #elif CONFIG_KEYPAD == MA_PAD @@ -546,7 +546,7 @@ F3: equal to "=" #define CALCULATOR_CALC BUTTON_X #define CALCULATOR_CLEAR BUTTON_B -#elif CONFIG_KEYPAD == CTRU_PAD +#elif CONFIG_KEYPAD == N3DS_PAD #define CALCULATOR_LEFT BUTTON_LEFT #define CALCULATOR_RIGHT BUTTON_RIGHT @@ -557,6 +557,17 @@ F3: equal to "=" #define CALCULATOR_CALC BUTTON_MENU #define CALCULATOR_CLEAR BUTTON_USER +#elif CONFIG_KEYPAD == ECHO_R1_PAD + +#define CALCULATOR_LEFT BUTTON_LEFT +#define CALCULATOR_RIGHT BUTTON_RIGHT +#define CALCULATOR_UP BUTTON_UP +#define CALCULATOR_DOWN BUTTON_DOWN +#define CALCULATOR_QUIT BUTTON_POWER +#define CALCULATOR_INPUT BUTTON_A +#define CALCULATOR_CALC BUTTON_B +#define CALCULATOR_CLEAR BUTTON_X + #else #error No keymap defined! #endif diff --git a/apps/plugins/calendar.c b/apps/plugins/calendar.c index f605c3267c..37f3d75d33 100644 --- a/apps/plugins/calendar.c +++ b/apps/plugins/calendar.c @@ -408,7 +408,7 @@ #define CALENDAR_NEXT_MONTH BUTTON_VOL_UP #define CALENDAR_PREV_MONTH BUTTON_VOL_DOWN -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD /* use touchscreen */ #elif CONFIG_KEYPAD == MA_PAD @@ -432,7 +432,7 @@ #define CALENDAR_PREV_MONTH BUTTON_L #define CALENDAR_EVENT_MENU_NAME "A" -#elif CONFIG_KEYPAD == CTRU_PAD +#elif CONFIG_KEYPAD == N3DS_PAD #define CALENDAR_QUIT BUTTON_BACK #define CALENDAR_SELECT BUTTON_SELECT #define CALENDAR_NEXT_WEEK BUTTON_DOWN @@ -442,6 +442,16 @@ #define CALENDAR_NEXT_MONTH BUTTON_POWER #define CALENDAR_PREV_MONTH BUTTON_USER +#elif CONFIG_KEYPAD == ECHO_R1_PAD +#define CALENDAR_QUIT BUTTON_POWER +#define CALENDAR_SELECT BUTTON_A +#define CALENDAR_NEXT_WEEK BUTTON_DOWN +#define CALENDAR_PREV_WEEK BUTTON_UP +#define CALENDAR_NEXT_DAY BUTTON_RIGHT +#define CALENDAR_PREV_DAY BUTTON_LEFT +#define CALENDAR_NEXT_MONTH BUTTON_B +#define CALENDAR_PREV_MONTH BUTTON_Y + #else #error "No keypad setting." #endif diff --git a/apps/plugins/chessbox/chessbox_pgn.h b/apps/plugins/chessbox/chessbox_pgn.h index 8d621dcfb7..f4f505ef20 100644 --- a/apps/plugins/chessbox/chessbox_pgn.h +++ b/apps/plugins/chessbox/chessbox_pgn.h @@ -566,7 +566,7 @@ #define CB_SCROLL_LEFT (BUTTON_LEFT|BUTTON_REPEAT) #define CB_SCROLL_RIGHT (BUTTON_RIGHT|BUTTON_REPEAT) -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD /* use touchscreen */ #elif CONFIG_KEYPAD == MA_PAD @@ -599,7 +599,7 @@ #define CB_SCROLL_RIGHT (BUTTON_RIGHT|BUTTON_REPEAT) #define CB_RC_QUIT BUTTON_START -#elif CONFIG_KEYPAD == CTRU_PAD +#elif CONFIG_KEYPAD == N3DS_PAD #define CB_SELECT BUTTON_SELECT #define CB_UP BUTTON_UP #define CB_DOWN BUTTON_DOWN @@ -613,6 +613,20 @@ #define CB_SCROLL_LEFT (BUTTON_LEFT|BUTTON_REPEAT) #define CB_SCROLL_RIGHT (BUTTON_RIGHT|BUTTON_REPEAT) +#elif CONFIG_KEYPAD == ECHO_R1_PAD +#define CB_SELECT BUTTON_A +#define CB_UP BUTTON_UP +#define CB_DOWN BUTTON_DOWN +#define CB_LEFT BUTTON_LEFT +#define CB_RIGHT BUTTON_RIGHT +#define CB_PLAY BUTTON_START +#define CB_LEVEL BUTTON_SELECT +#define CB_MENU BUTTON_POWER +#define CB_SCROLL_UP (BUTTON_UP|BUTTON_REPEAT) +#define CB_SCROLL_DOWN (BUTTON_DOWN|BUTTON_REPEAT) +#define CB_SCROLL_LEFT (BUTTON_LEFT|BUTTON_REPEAT) +#define CB_SCROLL_RIGHT (BUTTON_RIGHT|BUTTON_REPEAT) + #else #error No keymap defined! #endif diff --git a/apps/plugins/chessclock.c b/apps/plugins/chessclock.c index 9de3a78229..971af2d90e 100644 --- a/apps/plugins/chessclock.c +++ b/apps/plugins/chessclock.c @@ -384,7 +384,7 @@ #define CHC_SETTINGS_OK BUTTON_SELECT #define CHC_SETTINGS_CANCEL BUTTON_POWER -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD #define CHC_QUIT BUTTON_POWER #elif (CONFIG_KEYPAD == MA_PAD) @@ -407,7 +407,7 @@ #define CHC_SETTINGS_OK BUTTON_A #define CHC_SETTINGS_CANCEL BUTTON_B -#elif CONFIG_KEYPAD == CTRU_PAD +#elif CONFIG_KEYPAD == N3DS_PAD #define CHC_QUIT BUTTON_BACK #define CHC_STARTSTOP BUTTON_SELECT #define CHC_RESET BUTTON_USER @@ -417,6 +417,16 @@ #define CHC_SETTINGS_OK BUTTON_SELECT #define CHC_SETTINGS_CANCEL BUTTON_BACK +#elif CONFIG_KEYPAD == ECHO_R1_PAD +#define CHC_QUIT BUTTON_POWER +#define CHC_STARTSTOP BUTTON_START +#define CHC_RESET BUTTON_SELECT +#define CHC_MENU BUTTON_X +#define CHC_SETTINGS_INC BUTTON_UP +#define CHC_SETTINGS_DEC BUTTON_DOWN +#define CHC_SETTINGS_OK BUTTON_A +#define CHC_SETTINGS_CANCEL BUTTON_B + #else #error No keymap defined! #endif diff --git a/apps/plugins/chip8.c b/apps/plugins/chip8.c index d791d8408b..bf22fcb0b9 100644 --- a/apps/plugins/chip8.c +++ b/apps/plugins/chip8.c @@ -1301,7 +1301,7 @@ CONFIG_KEYPAD == MROBE500_PAD #define CHIP8_KEY9 BUTTON_R #define CHIP8_KEY0 BUTTON_L -#elif (CONFIG_KEYPAD == CTRU_PAD) +#elif (CONFIG_KEYPAD == N3DS_PAD) #define CHIP8_OFF (BUTTON_BACK|BUTTON_REPEAT) #define CHIP8_KEY1 BUTTON_MENU #define CHIP8_KEY2 BUTTON_UP @@ -1313,7 +1313,20 @@ CONFIG_KEYPAD == MROBE500_PAD #define CHIP8_KEY8 BUTTON_POWER #define CHIP8_KEY9 BUTTON_USER -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif (CONFIG_KEYPAD == ECHO_R1_PAD) +#define CHIP8_OFF BUTTON_POWER +#define CHIP8_KEY1 BUTTON_Y +#define CHIP8_KEY2 BUTTON_UP +#define CHIP8_KEY3 BUTTON_B +#define CHIP8_KEY4 BUTTON_LEFT +#define CHIP8_KEY5 BUTTON_A +#define CHIP8_KEY6 BUTTON_RIGHT +#define CHIP8_KEY7 BUTTON_SELECT +#define CHIP8_KEY8 BUTTON_DOWN +#define CHIP8_KEY9 BUTTON_START +#define CHIP8_KEYA BUTTON_X + +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD /* use touchscreen */ #else diff --git a/apps/plugins/chopper.c b/apps/plugins/chopper.c index f31e834b4c..cb4816c2f6 100644 --- a/apps/plugins/chopper.c +++ b/apps/plugins/chopper.c @@ -206,12 +206,18 @@ CONFIG_KEYPAD == MROBE500_PAD #define ACTION2 BUTTON_B #define ACTIONTEXT "A" -#elif CONFIG_KEYPAD == CTRU_PAD +#elif CONFIG_KEYPAD == N3DS_PAD #define QUIT BUTTON_BACK #define ACTION BUTTON_SELECT #define ACTION2 BUTTON_MENU #define ACTIONTEXT "A" +#elif CONFIG_KEYPAD == ECHO_R1_PAD +#define QUIT BUTTON_POWER +#define ACTION BUTTON_A +#define ACTION2 BUTTON_UP +#define ACTIONTEXT "A" + #elif !defined(HAVE_TOUCHSCREEN) #error No keymap defined! #endif diff --git a/apps/plugins/clix.c b/apps/plugins/clix.c index b8e8d2164b..6309cf1e83 100644 --- a/apps/plugins/clix.c +++ b/apps/plugins/clix.c @@ -305,7 +305,7 @@ #define CLIX_BUTTON_RIGHT BUTTON_RIGHT #define CLIX_BUTTON_CLICK BUTTON_SELECT -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD #define CLIX_BUTTON_QUIT BUTTON_POWER #elif CONFIG_KEYPAD == MA_PAD @@ -324,7 +324,7 @@ #define CLIX_BUTTON_UP BUTTON_UP #define CLIX_BUTTON_DOWN BUTTON_DOWN -#elif (CONFIG_KEYPAD == CTRU_PAD) +#elif (CONFIG_KEYPAD == N3DS_PAD) #define CLIX_BUTTON_QUIT BUTTON_BACK #define CLIX_BUTTON_LEFT BUTTON_LEFT #define CLIX_BUTTON_RIGHT BUTTON_RIGHT @@ -332,6 +332,14 @@ #define CLIX_BUTTON_UP BUTTON_UP #define CLIX_BUTTON_DOWN BUTTON_DOWN +#elif (CONFIG_KEYPAD == ECHO_R1_PAD) +#define CLIX_BUTTON_QUIT BUTTON_POWER +#define CLIX_BUTTON_LEFT BUTTON_LEFT +#define CLIX_BUTTON_RIGHT BUTTON_RIGHT +#define CLIX_BUTTON_CLICK BUTTON_A +#define CLIX_BUTTON_UP BUTTON_UP +#define CLIX_BUTTON_DOWN BUTTON_DOWN + #else #error "no keymap" #endif diff --git a/apps/plugins/clock/clock_settings.c b/apps/plugins/clock/clock_settings.c index 6e6f32bd36..e5a3c61cf0 100644 --- a/apps/plugins/clock/clock_settings.c +++ b/apps/plugins/clock/clock_settings.c @@ -117,7 +117,7 @@ static enum settings_file_status clock_settings_load( if(fd >= 0) { /* basic consistency check */ - if(rb->filesize(fd) != sizeof(*settings)) + if(rb->ffilesize(fd) != sizeof(*settings)) rb->close(fd); else { diff --git a/apps/plugins/crypt_firmware.c b/apps/plugins/crypt_firmware.c index 332d18e761..945781cdea 100644 --- a/apps/plugins/crypt_firmware.c +++ b/apps/plugins/crypt_firmware.c @@ -150,7 +150,7 @@ enum plugin_status plugin_start(const void* parameter) return PLUGIN_ERROR; } - length = rb->filesize(fd); + length = rb->ffilesize(fd); if (length < 12) { rb->splash(HZ*2, "File too small"); diff --git a/apps/plugins/cube.c b/apps/plugins/cube.c index 5ea60363c5..3936bca565 100644 --- a/apps/plugins/cube.c +++ b/apps/plugins/cube.c @@ -391,7 +391,7 @@ #define CUBE_PAUSE BUTTON_PLAY #define CUBE_HIGHSPEED BUTTON_BACK -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD /* use touchscreen */ #elif CONFIG_KEYPAD == MA_PAD @@ -414,7 +414,7 @@ #define CUBE_PAUSE BUTTON_A #define CUBE_HIGHSPEED BUTTON_Y -#elif (CONFIG_KEYPAD == CTRU_PAD) +#elif (CONFIG_KEYPAD == N3DS_PAD) #define CUBE_QUIT BUTTON_BACK #define CUBE_NEXT BUTTON_RIGHT #define CUBE_PREV BUTTON_LEFT @@ -424,6 +424,16 @@ #define CUBE_PAUSE BUTTON_USER #define CUBE_HIGHSPEED BUTTON_SELECT +#elif (CONFIG_KEYPAD == ECHO_R1_PAD) +#define CUBE_QUIT BUTTON_POWER +#define CUBE_NEXT BUTTON_RIGHT +#define CUBE_PREV BUTTON_LEFT +#define CUBE_INC BUTTON_UP +#define CUBE_DEC BUTTON_DOWN +#define CUBE_MODE BUTTON_SELECT +#define CUBE_PAUSE BUTTON_START +#define CUBE_HIGHSPEED BUTTON_A + #else #error No keymap defined! #endif diff --git a/apps/plugins/dict.c b/apps/plugins/dict.c index fe61229c7e..c679914b9b 100644 --- a/apps/plugins/dict.c +++ b/apps/plugins/dict.c @@ -102,7 +102,7 @@ enum plugin_status plugin_start(const void* parameter) return PLUGIN_ERROR; } - filesize = rb->filesize(fIndex); /* get filesize */ + filesize = rb->ffilesize(fIndex); /* get filesize */ DEBUGF("Filesize: %d bytes = %d words \n", filesize, (filesize / (int)sizeof(struct stWord))); diff --git a/apps/plugins/disktidy.c b/apps/plugins/disktidy.c index 993a68a2db..c65be33d43 100644 --- a/apps/plugins/disktidy.c +++ b/apps/plugins/disktidy.c @@ -51,6 +51,12 @@ struct run_statistics { #endif }; +struct stats_text { + struct viewport vp; + char** arr; + int len; +}; + struct tidy_type { char filestring[64]; int pre; @@ -223,9 +229,23 @@ static bool load_run_stats(void) return load_success; } +static void display_run_stats_cb(unsigned short id, void *data, void *userdata) +{ + (void)id; + (void)data; + struct stats_text *text = (struct stats_text *) userdata; + display_text(text->len, text->arr, NULL, &text->vp, false); +} + +static void display_run_stats_cleanup(void *userdata) +{ + struct stats_text *text = (struct stats_text *) userdata; + rb->remove_event_ex(GUI_EVENT_NEED_UI_UPDATE, display_run_stats_cb, text); +} + static enum plugin_status display_run_stats(void) { - struct viewport vp; + struct stats_text text; if (!load_run_stats()) { rb->splash(HZ * 2, "Unable to load last run stats"); @@ -307,22 +327,22 @@ static enum plugin_status display_run_stats(void) dirs_removed , run_stats.dirs_removed == 1 ? "dir" : "dirs", "", run_time , "", }; - char** text_arr = last_run_text; - int len = ARRAYLEN(last_run_text); + text.arr = last_run_text; + text.len = ARRAYLEN(last_run_text); if (!num_removed) { /* Hide superfluous zeros if nothing was removed */ - text_arr[len - 9] = ""; - text_arr[len - 8] = run_time; - text_arr[len - 7] = ""; - len -= 6; + text.arr[text.len - 9] = ""; + text.arr[text.len - 8] = run_time; + text.arr[text.len - 7] = ""; + text.len -= 6; } #if CONFIG_RTC sbs_has_title = rb->sb_set_title_text(last_run, Icon_NOICON, SCREEN_MAIN); if (sbs_has_title) { - text_arr += 2; - len -=2; + text.arr += 2; + text.len -=2; #else sbs_has_title = rb->sb_set_title_text("Last Run", Icon_NOICON, SCREEN_MAIN); if (sbs_has_title) @@ -331,21 +351,23 @@ static enum plugin_status display_run_stats(void) rb->send_event(GUI_EVENT_ACTIONREDRAW, (void*)1); } - rb->viewport_set_defaults(&vp, SCREEN_MAIN); + rb->viewport_set_defaults(&text.vp, SCREEN_MAIN); - if (display_text(len, text_arr, NULL, &vp, false)) - { + if (display_text(text.len, text.arr, NULL, &text.vp, false)) return PLUGIN_USB_CONNECTED; - } + + rb->add_event_ex(GUI_EVENT_NEED_UI_UPDATE, false, display_run_stats_cb, &text); while (true) /* keep info on screen until cancelled */ { int button = rb->get_action(CONTEXT_STD, HZ/2); if (button == ACTION_STD_CANCEL || button == ACTION_STD_MENU) break; - if (rb->default_event_handler(button) == SYS_USB_CONNECTED) + if (rb->default_event_handler_ex(button, display_run_stats_cleanup, &text) + == SYS_USB_CONNECTED) return PLUGIN_USB_CONNECTED; } + display_run_stats_cleanup(&text); return PLUGIN_OK; } diff --git a/apps/plugins/doom/d_deh.c b/apps/plugins/doom/d_deh.c index 92ea91b6ae..f8eae5af01 100644 --- a/apps/plugins/doom/d_deh.c +++ b/apps/plugins/doom/d_deh.c @@ -106,7 +106,7 @@ int dehfeof(DEHFILE *fp) { if (fp->fd >= 0) { - off_t size = filesize(fp->fd); + off_t size = ffilesize(fp->fd); off_t offset = lseek(fp->fd, 0, SEEK_CUR); return (size <= 0 || offset < 0 || offset >= size) ? 1 : 0; } diff --git a/apps/plugins/doom/i_video.c b/apps/plugins/doom/i_video.c index a66bf25852..e5cca93d31 100644 --- a/apps/plugins/doom/i_video.c +++ b/apps/plugins/doom/i_video.c @@ -616,7 +616,7 @@ void I_ShutdownGraphics(void) #define DOOMBUTTON_WEAPON BUTTON_BACK #define DOOMBUTTON_MAP (BUTTON_BACK|BUTTON_REPEAT) -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD #define DOOMBUTTON_ESC BUTTON_POWER #define DOOMBUTTON_MAP BUTTON_PREV @@ -632,7 +632,7 @@ void I_ShutdownGraphics(void) #define DOOMBUTTON_WEAPON BUTTON_X #define DOOMBUTTON_MAP BUTTON_Y -#elif CONFIG_KEYPAD == CTRU_PAD +#elif CONFIG_KEYPAD == N3DS_PAD #define DOOMBUTTON_UP BUTTON_UP #define DOOMBUTTON_DOWN BUTTON_DOWN #define DOOMBUTTON_LEFT BUTTON_LEFT @@ -643,6 +643,18 @@ void I_ShutdownGraphics(void) #define DOOMBUTTON_ENTER BUTTON_POWER #define DOOMBUTTON_WEAPON BUTTON_USER +#elif CONFIG_KEYPAD == ECHO_R1_PAD +#define DOOMBUTTON_UP BUTTON_UP +#define DOOMBUTTON_DOWN BUTTON_DOWN +#define DOOMBUTTON_LEFT BUTTON_LEFT +#define DOOMBUTTON_RIGHT BUTTON_RIGHT +#define DOOMBUTTON_SHOOT BUTTON_A +#define DOOMBUTTON_OPEN BUTTON_B +#define DOOMBUTTON_ESC BUTTON_POWER +#define DOOMBUTTON_ENTER BUTTON_X +#define DOOMBUTTON_WEAPON BUTTON_Y +#define DOOMBUTTON_MAP BUTTON_START + #else #error Keymap not defined! #endif diff --git a/apps/plugins/doom/m_misc.c b/apps/plugins/doom/m_misc.c index 0318c9d741..9d1d33ffcf 100644 --- a/apps/plugins/doom/m_misc.c +++ b/apps/plugins/doom/m_misc.c @@ -127,7 +127,7 @@ int M_ReadFile(char const* name,byte** buffer) if ((handle < 0)) I_Error ("M_ReadFile: Couldn't read file %s", name); - length = filesize(handle); + length = ffilesize(handle); buf = Z_Malloc (length, PU_STATIC, NULL); count = read (handle, buf, length); close (handle); diff --git a/apps/plugins/doom/rockmacros.h b/apps/plugins/doom/rockmacros.h index d46d5a22fc..7f01cf2f95 100644 --- a/apps/plugins/doom/rockmacros.h +++ b/apps/plugins/doom/rockmacros.h @@ -52,7 +52,7 @@ int my_close(int id); #endif #define lseek(a,b,c) rb->lseek((a),(b),(c)) -#define filesize(a) rb->filesize((a)) +#define ffilesize(a) rb->ffilesize((a)) #define read(a,b,c) rb->read((a),(b),(c)) #define write(a,b,c) rb->write((a),(b),(c)) #undef strtok diff --git a/apps/plugins/doom/w_wad.c b/apps/plugins/doom/w_wad.c index 48d695c3b4..3b262d62f8 100644 --- a/apps/plugins/doom/w_wad.c +++ b/apps/plugins/doom/w_wad.c @@ -87,7 +87,7 @@ static int W_Filelength(int handle) I_Error("W_Filelength: Error fstating"); return fileinfo.st_size; */ - return filesize(handle); + return ffilesize(handle); } void ExtractFileBase (const char *path, char *dest) diff --git a/apps/plugins/fft/fft.c b/apps/plugins/fft/fft.c index 18aefe2503..52a3d277fa 100644 --- a/apps/plugins/fft/fft.c +++ b/apps/plugins/fft/fft.c @@ -1284,7 +1284,7 @@ enum plugin_status plugin_start(const void* parameter) rb->yield(); /* tmo = 0 won't yield */ } - int button = pluginlib_getaction(TIMEOUT_NOBLOCK, plugin_contexts, ARRAYLEN(plugin_contexts)); + int button = pluginlib_getaction(delay, plugin_contexts, ARRAYLEN(plugin_contexts)); switch (button) { diff --git a/apps/plugins/flipit.c b/apps/plugins/flipit.c index 42a1e4b9af..c33a22a709 100644 --- a/apps/plugins/flipit.c +++ b/apps/plugins/flipit.c @@ -481,7 +481,7 @@ #define FLIPIT_STEP_BY_STEP BUTTON_VOL_UP #define FLIPIT_TOGGLE BUTTON_SELECT -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD /* use touchscreen */ #elif CONFIG_KEYPAD == MA_PAD @@ -510,7 +510,7 @@ #define FLIPIT_STEP_BY_STEP BUTTON_Y #define FLIPIT_TOGGLE BUTTON_A -#elif CONFIG_KEYPAD == CTRU_PAD +#elif CONFIG_KEYPAD == N3DS_PAD #define FLIPIT_LEFT BUTTON_LEFT #define FLIPIT_RIGHT BUTTON_RIGHT @@ -522,6 +522,18 @@ #define FLIPIT_STEP_BY_STEP BUTTON_POWER #define FLIPIT_TOGGLE BUTTON_SELECT +#elif CONFIG_KEYPAD == ECHO_R1_PAD + +#define FLIPIT_LEFT BUTTON_LEFT +#define FLIPIT_RIGHT BUTTON_RIGHT +#define FLIPIT_UP BUTTON_UP +#define FLIPIT_DOWN BUTTON_DOWN +#define FLIPIT_QUIT BUTTON_POWER +#define FLIPIT_SHUFFLE BUTTON_SELECT +#define FLIPIT_SOLVE BUTTON_START +#define FLIPIT_STEP_BY_STEP BUTTON_X +#define FLIPIT_TOGGLE BUTTON_A + #else #error No keymap defined! #endif diff --git a/apps/plugins/fractals/fractal.h b/apps/plugins/fractals/fractal.h index 79324a507b..f1f5bee424 100644 --- a/apps/plugins/fractals/fractal.h +++ b/apps/plugins/fractals/fractal.h @@ -491,7 +491,7 @@ #define FRACTAL_PRECISION_DEC BUTTON_BACK #define FRACTAL_RESET BUTTON_PLAY -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD #define FRACTAL_QUIT BUTTON_POWER #elif CONFIG_KEYPAD == MA_PAD @@ -518,7 +518,7 @@ #define FRACTAL_PRECISION_DEC BUTTON_L #define FRACTAL_RESET BUTTON_A -#elif CONFIG_KEYPAD == CTRU_PAD +#elif CONFIG_KEYPAD == N3DS_PAD #define FRACTAL_QUIT BUTTON_BACK #define FRACTAL_UP BUTTON_UP #define FRACTAL_DOWN BUTTON_DOWN @@ -530,6 +530,18 @@ #define FRACTAL_PRECISION_DEC (BUTTON_USER|BUTTON_REPEAT) #define FRACTAL_RESET BUTTON_POWER +#elif CONFIG_KEYPAD == ECHO_R1_PAD +#define FRACTAL_QUIT BUTTON_POWER +#define FRACTAL_UP BUTTON_UP +#define FRACTAL_DOWN BUTTON_DOWN +#define FRACTAL_LEFT BUTTON_LEFT +#define FRACTAL_RIGHT BUTTON_RIGHT +#define FRACTAL_ZOOM_IN BUTTON_A +#define FRACTAL_ZOOM_OUT BUTTON_B +#define FRACTAL_PRECISION_INC BUTTON_Y +#define FRACTAL_PRECISION_DEC BUTTON_X +#define FRACTAL_RESET BUTTON_START + #else #error No keymap defined! #endif diff --git a/apps/plugins/goban/goban.h b/apps/plugins/goban/goban.h index 80d69cb9e1..d6c68178ae 100644 --- a/apps/plugins/goban/goban.h +++ b/apps/plugins/goban/goban.h @@ -509,7 +509,7 @@ #define GBN_BUTTON_CONTEXT BUTTON_X #define GBN_BUTTON_NEXT_VAR BUTTON_Y -#elif (CONFIG_KEYPAD == CTRU_PAD) +#elif (CONFIG_KEYPAD == N3DS_PAD) #define GBN_BUTTON_UP BUTTON_UP #define GBN_BUTTON_DOWN BUTTON_DOWN #define GBN_BUTTON_LEFT BUTTON_LEFT @@ -519,6 +519,16 @@ #define GBN_BUTTON_PLAY BUTTON_SELECT #define GBN_BUTTON_MENU BUTTON_MENU +#elif (CONFIG_KEYPAD == ECHO_R1_PAD) +#define GBN_BUTTON_UP BUTTON_UP +#define GBN_BUTTON_DOWN BUTTON_DOWN +#define GBN_BUTTON_LEFT BUTTON_LEFT +#define GBN_BUTTON_RIGHT BUTTON_RIGHT +#define GBN_BUTTON_RETREAT BUTTON_X +#define GBN_BUTTON_ADVANCE BUTTON_Y +#define GBN_BUTTON_PLAY BUTTON_A +#define GBN_BUTTON_MENU BUTTON_POWER + #else #error Unsupported keypad #endif diff --git a/apps/plugins/h264_player/SOURCES b/apps/plugins/h264_player/SOURCES new file mode 100644 index 0000000000..0cf6cd667a --- /dev/null +++ b/apps/plugins/h264_player/SOURCES @@ -0,0 +1,5 @@ +mp4_demux.c +video_audio.c +video_pcm.c +video_playback.c +h264_player.c diff --git a/apps/plugins/h264_player/h264_player.c b/apps/plugins/h264_player/h264_player.c new file mode 100644 index 0000000000..d76bf72379 --- /dev/null +++ b/apps/plugins/h264_player/h264_player.c @@ -0,0 +1,51 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2026 David Cormier + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#include "plugin.h" +#include "video_playback.h" + +enum plugin_status plugin_start(const void *parameter) +{ + const char *path = parameter; + size_t buffer_size = 0; + void *buffer; + int result; + + if (path == NULL || path[0] == '\0') + { + rb->splash(HZ * 2, "No video file"); + return PLUGIN_ERROR; + } + + buffer = rb->plugin_get_audio_buffer(&buffer_size); + if (buffer == NULL || buffer_size == 0) + { + rb->splash(HZ * 2, "Not enough video memory"); + return PLUGIN_ERROR; + } + + result = video_h264_play(path, buffer, buffer_size); + rb->button_clear_queue(); + rb->plugin_release_audio_buffer(); + + if (result == 2) + return PLUGIN_USB_CONNECTED; + return result < 0 ? PLUGIN_ERROR : PLUGIN_OK; +} diff --git a/apps/plugins/h264_player/h264_player.make b/apps/plugins/h264_player/h264_player.make new file mode 100644 index 0000000000..ae0c2e0dd6 --- /dev/null +++ b/apps/plugins/h264_player/h264_player.make @@ -0,0 +1,26 @@ +# __________ __ ___. +# Open \______ \ ____ ____ | | _\_ |__ _______ ___ +# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / +# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < +# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ +# \/ \/ \/ \/ \/ +# + +H264PLAYERSRCDIR := $(APPSDIR)/plugins/h264_player +H264PLAYERBUILDDIR := $(BUILDDIR)/apps/plugins/h264_player + +ROCKS += $(H264PLAYERBUILDDIR)/h264_player.rock + +H264PLAYER_SRC := $(call preprocess, $(H264PLAYERSRCDIR)/SOURCES) +H264PLAYER_OBJ := $(call c2obj, $(H264PLAYER_SRC)) + +# add source files to OTHER_SRC to get automatic dependencies +OTHER_SRC += $(H264PLAYER_SRC) + +$(H264PLAYERBUILDDIR)/h264_player.rock: $(H264PLAYER_OBJ) + +$(H264PLAYERBUILDDIR)/%.o: $(H264PLAYERSRCDIR)/%.c \ + $(H264PLAYERSRCDIR)/h264_player.make + $(SILENT)mkdir -p $(dir $@) + $(call PRINTS,CC $(subst $(ROOTDIR)/,,$<))$(CC) -I$(dir $<) \ + $(PLUGINFLAGS) -c $< -o $@ diff --git a/apps/plugins/h264_player/mp4_demux.c b/apps/plugins/h264_player/mp4_demux.c new file mode 100644 index 0000000000..53fee61c71 --- /dev/null +++ b/apps/plugins/h264_player/mp4_demux.c @@ -0,0 +1,1548 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2025-2026 David Cormier + * + * MP4 video demuxer - extracts H.264 video track data from MP4/M4V files. + * Based on libm4a/demux.c box-walking pattern, with fd-based I/O and + * video track support (vmhd + avc1/avcC parsing). + * + * Original demux.c copyright: + * Copyright (c) 2005 David Hammerton + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#include "plugin.h" +#include "system.h" +#include "file.h" +#include "rbendian.h" +#include "mp4_demux.h" + +#include + +/* --- Simple fd-based stream I/O --- */ + +struct mp4_stream { + int fd; + off_t pos; + off_t size; + bool eof; +}; + +static void stream_init(struct mp4_stream *s, int fd, off_t size) +{ + s->fd = fd; + s->pos = 0; + s->size = size; + s->eof = false; +} + +static bool stream_read(struct mp4_stream *s, void *buf, size_t len) +{ + size_t done = 0; + + if (s->pos < 0 || s->pos > s->size || + len > (size_t)(s->size - s->pos)) + { + s->eof = true; + return false; + } + + while (done < len) + { + ssize_t count = rb->read(s->fd, (uint8_t *)buf + done, len - done); + + if (count <= 0) + { + s->eof = true; + return false; + } + done += (size_t)count; + s->pos += count; + } + return true; +} + +static uint32_t stream_read_uint32(struct mp4_stream *s) +{ + uint8_t buf[4]; + if (!stream_read(s, buf, 4)) + return 0; + /* big-endian */ + return ((uint32_t)buf[0] << 24) | ((uint32_t)buf[1] << 16) + | ((uint32_t)buf[2] << 8) | (uint32_t)buf[3]; +} + +static uint16_t stream_read_uint16(struct mp4_stream *s) +{ + uint8_t buf[2]; + if (!stream_read(s, buf, 2)) + return 0; + return ((uint16_t)buf[0] << 8) | (uint16_t)buf[1]; +} + +static uint8_t stream_read_uint8(struct mp4_stream *s) +{ + uint8_t b; + if (!stream_read(s, &b, 1)) + return 0; + return b; +} + +static bool stream_read_uint32_array(struct mp4_stream *s, uint32_t *values, + uint32_t count) +{ + uint32_t i; + + if (!stream_read(s, values, (size_t)count * sizeof(*values))) + return false; + for (i = 0; i < count; i++) + values[i] = betoh32(values[i]); + return true; +} + +static void stream_skip(struct mp4_stream *s, size_t n) +{ + if (s->pos < 0 || s->pos > s->size || + n > (size_t)(s->size - s->pos)) + { + s->eof = true; + return; + } + s->pos = rb->lseek(s->fd, (off_t)n, SEEK_CUR); + if (s->pos < 0) + s->eof = true; +} + +static off_t stream_tell(struct mp4_stream *s) +{ + return s->pos; +} + +/* --- Box parsers --- */ + +struct mp4_parse_ctx { + struct mp4_stream stream; + struct mp4v_demux_res *res; + bool found_video; /* set when we find a video track */ + bool found_audio; /* set when we find an audio track */ + bool in_video_trak; /* currently inside a video trak */ + bool in_audio_trak; /* currently inside an audio trak */ + uint16_t tkhd_dw; /* tkhd display width (pending video confirm) */ + uint16_t tkhd_dh; /* tkhd display height (pending video confirm) */ + /* Pending per-trak fields (committed at end of trak once type is known) */ + uint32_t pending_timescale; /* from mdhd (parsed before hdlr) */ + uint32_t pending_lead_trim; /* from edts/elst (parsed before mdia) */ +}; + +/* Parse avcC box - H.264 decoder configuration record. + * Layout: configurationVersion(1) + AVCProfileIndication(1) + + * profile_compatibility(1) + AVCLevelIndication(1) + + * lengthSizeMinusOne(1, lower 2 bits) + + * numSPS(1, lower 5 bits) + [spsLen(2)+spsData]... + + * numPPS(1) + [ppsLen(2)+ppsData]... */ +static bool read_chunk_avcc(struct mp4_parse_ctx *ctx, size_t chunk_len) +{ + size_t data_len; + uint8_t config_ver; + + if (chunk_len < 8) + return false; + data_len = chunk_len - 8; + if (data_len > MP4V_MAX_CODECDATA || data_len < 7) + return false; + + /* Read the entire avcC payload into codecdata */ + ctx->res->codecdata_len = data_len; + if (!stream_read(&ctx->stream, ctx->res->codecdata, data_len)) + return false; + + /* Parse key fields from the buffer */ + config_ver = ctx->res->codecdata[0]; + if (config_ver != 1) + return false; + + ctx->res->avc_profile = ctx->res->codecdata[1]; + ctx->res->avc_level = ctx->res->codecdata[3]; + ctx->res->nalu_len_size = (ctx->res->codecdata[4] & 0x03) + 1; + ctx->res->num_sps = ctx->res->codecdata[5] & 0x1F; + + /* Count PPS: skip past SPS entries to find numPPS */ + { + size_t offset = 6; + uint8_t i; + + for (i = 0; i < ctx->res->num_sps && offset + 2 <= data_len; i++) + { + uint16_t sps_len = ((uint16_t)ctx->res->codecdata[offset] << 8) + | ctx->res->codecdata[offset + 1]; + offset += 2 + sps_len; + } + + if (offset < data_len) + ctx->res->num_pps = ctx->res->codecdata[offset]; + } + + return true; +} + +/* Parse stsd for video: expects avc1 sample entry */ +static bool read_chunk_stsd_video(struct mp4_parse_ctx *ctx, size_t chunk_len) +{ + size_t size_remaining; + uint32_t numentries, i; + + if (chunk_len < 16) + return false; + size_remaining = chunk_len - 8; + /* version + flags */ + stream_read_uint32(&ctx->stream); + size_remaining -= 4; + + numentries = stream_read_uint32(&ctx->stream); + size_remaining -= 4; + + for (i = 0; i < numentries && size_remaining > 8; i++) + { + uint32_t entry_size = stream_read_uint32(&ctx->stream); + uint32_t format = stream_read_uint32(&ctx->stream); + uint32_t entry_remaining = entry_size - 8; + + if (entry_size <= 8 || entry_size > size_remaining) + return false; + + if (format == MAKEFOURCC('a','v','c','1') + || format == MAKEFOURCC('a','v','c','3')) + { + if (entry_remaining < 78) + return false; + ctx->res->format = format; + + /* reserved(6) + data_ref_index(2) */ + stream_skip(&ctx->stream, 8); + entry_remaining -= 8; + + /* pre-defined(2) + reserved(2) + pre-defined(12) */ + stream_skip(&ctx->stream, 16); + entry_remaining -= 16; + + ctx->res->width = stream_read_uint16(&ctx->stream); + ctx->res->height = stream_read_uint16(&ctx->stream); + entry_remaining -= 4; + + /* horiz_resolution(4) + vert_resolution(4) + reserved(4) + + frame_count(2) + compressor_name(32) + depth(2) + + pre_defined(2) */ + stream_skip(&ctx->stream, 50); + entry_remaining -= 50; + + /* Walk sub-boxes looking for avcC */ + while (entry_remaining > 8) + { + uint32_t sub_len = stream_read_uint32(&ctx->stream); + uint32_t sub_id = stream_read_uint32(&ctx->stream); + + if (sub_len <= 8 || sub_len > entry_remaining) + break; + + if (sub_id == MAKEFOURCC('a','v','c','C')) + { + if (!read_chunk_avcc(ctx, sub_len)) + return false; + } + else + { + stream_skip(&ctx->stream, sub_len - 8); + } + + entry_remaining -= sub_len; + } + + if (entry_remaining > 0) + stream_skip(&ctx->stream, entry_remaining); + } + else + { + /* Unknown video format, skip */ + stream_skip(&ctx->stream, entry_remaining); + } + + size_remaining -= entry_size; + } + + if (size_remaining > 0) + stream_skip(&ctx->stream, size_remaining); + + return ctx->res->format != 0; +} + +/* Read MPEG-4 descriptor length (variable length, 7 bits per byte, + * high bit means continue). + * Ported from libm4a/demux.c:78-92. */ +static uint32_t read_mp4_descr_length(struct mp4_stream *s) +{ + uint8_t b; + uint8_t num_bytes = 0; + uint32_t length = 0; + + do { + b = stream_read_uint8(s); + num_bytes++; + length = (length << 7) | (b & 0x7F); + } while ((b & 0x80) && num_bytes < 4); + + return length; +} + +/* Parse esds box - extract AudioSpecificConfig for AAC decoder init. + * Ported from libm4a/demux.c:95-154. */ +static bool read_chunk_esds(struct mp4_parse_ctx *ctx, size_t chunk_len) +{ + off_t box_end; + uint8_t tag; + uint32_t descr_len; + + if (chunk_len < 12) + return false; + box_end = ctx->stream.pos + (off_t)(chunk_len - 8); + + /* version + flags */ + stream_read_uint32(&ctx->stream); + + /* ES_Descriptor (tag 0x03) */ + tag = stream_read_uint8(&ctx->stream); + descr_len = read_mp4_descr_length(&ctx->stream); + (void)descr_len; + + if (tag == 0x03) + { + /* ES_ID(2) + streamPriority(1) */ + stream_skip(&ctx->stream, 3); + } + else + { + /* Non-standard: skip 2 bytes instead */ + stream_skip(&ctx->stream, 2); + } + + /* DecoderConfigDescriptor (tag 0x04) */ + tag = stream_read_uint8(&ctx->stream); + descr_len = read_mp4_descr_length(&ctx->stream); + (void)descr_len; + + if (tag != 0x04) + { + off_t remaining = box_end - ctx->stream.pos; + + if (remaining < 0) + return false; + stream_skip(&ctx->stream, (size_t)remaining); + return !ctx->stream.eof; + } + + /* objectTypeIndication(1) + streamType(1) + bufferSizeDB(3) + + maxBitrate(4) + avgBitrate(4) = 13 bytes */ + stream_skip(&ctx->stream, 13); + + /* DecoderSpecificInfo (tag 0x05) = AudioSpecificConfig */ + tag = stream_read_uint8(&ctx->stream); + descr_len = read_mp4_descr_length(&ctx->stream); + + if (tag == 0x05 && descr_len > 0) + { + if (descr_len > sizeof(ctx->res->audio_codecdata)) + descr_len = sizeof(ctx->res->audio_codecdata); + + ctx->res->audio_codecdata_len = descr_len; + stream_read(&ctx->stream, ctx->res->audio_codecdata, descr_len); + } + + /* Skip any trailing descriptors (SLConfigDescriptor etc.) */ + { + off_t remaining = box_end - ctx->stream.pos; + if (remaining > 0) + stream_skip(&ctx->stream, (size_t)remaining); + } + + return !ctx->stream.eof && ctx->stream.pos == box_end; +} + +/* Parse stsd for audio: expects mp4a sample entry. + * mp4a layout: reserved(6) + data_ref_index(2) + reserved(8) + + * channels(2) + sample_size(2) + packet_size(2) + sample_rate(4, 16.16 FP) + + * reserved(2) + sub-boxes (esds, etc.) */ +static bool read_chunk_stsd_audio(struct mp4_parse_ctx *ctx, size_t chunk_len) +{ + size_t size_remaining; + uint32_t numentries, i; + + if (chunk_len < 16) + return false; + size_remaining = chunk_len - 8; + stream_read_uint32(&ctx->stream); /* version + flags */ + size_remaining -= 4; + + numentries = stream_read_uint32(&ctx->stream); + size_remaining -= 4; + + for (i = 0; i < numentries && size_remaining > 8; i++) + { + uint32_t entry_size = stream_read_uint32(&ctx->stream); + uint32_t format = stream_read_uint32(&ctx->stream); + uint32_t entry_remaining = entry_size - 8; + + if (entry_size <= 8 || entry_size > size_remaining) + return false; + + if (format == MAKEFOURCC('m','p','4','a')) + { + if (entry_remaining < 28) + return false; + ctx->res->audio_format = format; + + /* reserved(6) + data_ref_index(2) */ + stream_skip(&ctx->stream, 8); + entry_remaining -= 8; + + /* reserved(8) */ + stream_skip(&ctx->stream, 8); + entry_remaining -= 8; + + /* channelCount(2) + sampleSize(2) + compressionID(2) + + packetSize(2) = 8 bytes */ + stream_skip(&ctx->stream, 8); + entry_remaining -= 8; + + /* sampleRate: 16.16 fixed-point, 4 bytes */ + { + uint32_t sr_fp = stream_read_uint32(&ctx->stream); + ctx->res->audio_sample_rate = sr_fp >> 16; + } + entry_remaining -= 4; + + /* Walk sub-boxes for esds */ + while (entry_remaining > 8) + { + uint32_t sub_len = stream_read_uint32(&ctx->stream); + uint32_t sub_id = stream_read_uint32(&ctx->stream); + + if (sub_len <= 8 || sub_len > entry_remaining) + break; + + if (sub_id == MAKEFOURCC('e','s','d','s')) + { + if (!read_chunk_esds(ctx, sub_len)) + return false; + } + else + { + stream_skip(&ctx->stream, sub_len - 8); + } + + entry_remaining -= sub_len; + } + + if (entry_remaining > 0) + stream_skip(&ctx->stream, entry_remaining); + } + else + { + stream_skip(&ctx->stream, entry_remaining); + } + + size_remaining -= entry_size; + } + + if (size_remaining > 0) + stream_skip(&ctx->stream, size_remaining); + + return true; +} + +static bool read_chunk_stsz_audio(struct mp4_parse_ctx *ctx, size_t chunk_len) +{ + size_t size_remaining; + uint32_t default_size; + uint32_t numsizes; + + if (chunk_len < 20) + return false; + size_remaining = chunk_len - 8; + + stream_read_uint32(&ctx->stream); + size_remaining -= 4; + + default_size = stream_read_uint32(&ctx->stream); + size_remaining -= 4; + + numsizes = stream_read_uint32(&ctx->stream); + size_remaining -= 4; + + if (default_size == 0 && numsizes > size_remaining / 4) + return false; + ctx->res->audio_num_samples = numsizes; + stream_skip(&ctx->stream, size_remaining); + return !ctx->stream.eof; +} + +static bool read_audio_table_count(struct mp4_parse_ctx *ctx, + size_t chunk_len, size_t entry_size, + uint32_t *count) +{ + size_t size_remaining; + uint32_t numentries; + + if (chunk_len < 16) + return false; + size_remaining = chunk_len - 8; + stream_read_uint32(&ctx->stream); + size_remaining -= 4; + numentries = stream_read_uint32(&ctx->stream); + size_remaining -= 4; + if (entry_size == 0 || numentries > size_remaining / entry_size) + return false; + *count = numentries; + stream_skip(&ctx->stream, size_remaining); + return !ctx->stream.eof; +} + +static bool read_chunk_stsc_audio(struct mp4_parse_ctx *ctx, size_t chunk_len) +{ + return read_audio_table_count(ctx, chunk_len, 12, + &ctx->res->audio_num_stsc); +} + +static bool read_chunk_stco_audio(struct mp4_parse_ctx *ctx, size_t chunk_len) +{ + return read_audio_table_count(ctx, chunk_len, 4, + &ctx->res->audio_num_stco); +} + +static bool read_chunk_co64_audio(struct mp4_parse_ctx *ctx, size_t chunk_len) +{ + return read_audio_table_count(ctx, chunk_len, 8, + &ctx->res->audio_num_stco); +} + +/* Audio sample table container */ +static bool read_chunk_stbl_audio(struct mp4_parse_ctx *ctx, size_t chunk_len) +{ + size_t size_remaining = chunk_len - 8; + + while (size_remaining >= 8) + { + uint32_t sub_len = stream_read_uint32(&ctx->stream); + uint32_t sub_id = stream_read_uint32(&ctx->stream); + + if (sub_len < 8 || sub_len > size_remaining) + return false; + + switch (sub_id) + { + case MAKEFOURCC('s','t','s','d'): + if (!read_chunk_stsd_audio(ctx, sub_len)) + return false; + break; + case MAKEFOURCC('s','t','s','z'): + if (!read_chunk_stsz_audio(ctx, sub_len)) + return false; + break; + case MAKEFOURCC('s','t','s','c'): + if (!read_chunk_stsc_audio(ctx, sub_len)) + return false; + break; + case MAKEFOURCC('s','t','c','o'): + if (!read_chunk_stco_audio(ctx, sub_len)) + return false; + break; + case MAKEFOURCC('c','o','6','4'): + if (!read_chunk_co64_audio(ctx, sub_len)) + return false; + break; + /* No stss for audio; all AAC frames are sync samples */ + default: + stream_skip(&ctx->stream, sub_len - 8); + break; + } + + size_remaining -= sub_len; + } + + return !ctx->stream.eof && size_remaining == 0; +} + +/* stts - time-to-sample table */ +static bool read_chunk_stts(struct mp4_parse_ctx *ctx, size_t chunk_len) +{ + size_t size_remaining; + uint32_t numentries, i; + + if (chunk_len < 16) + return false; + size_remaining = chunk_len - 8; + stream_read_uint32(&ctx->stream); /* version + flags */ + size_remaining -= 4; + + numentries = stream_read_uint32(&ctx->stream); + size_remaining -= 4; + + if (numentries > MP4V_MAX_STTS || numentries > size_remaining / 8) + return false; + + ctx->res->num_stts = numentries; + if (!stream_read(&ctx->stream, ctx->res->stts, + (size_t)numentries * sizeof(ctx->res->stts[0]))) + return false; + size_remaining -= (size_t)numentries * 8; + for (i = 0; i < numentries; i++) + { + ctx->res->stts[i].sample_count = + betoh32(ctx->res->stts[i].sample_count); + ctx->res->stts[i].sample_delta = + betoh32(ctx->res->stts[i].sample_delta); + } + + if (size_remaining > 0) + stream_skip(&ctx->stream, size_remaining); + + return true; +} + +/* stsz - sample sizes */ +static bool read_chunk_stsz(struct mp4_parse_ctx *ctx, size_t chunk_len) +{ + size_t size_remaining; + uint32_t default_size, numsizes, i; + + if (chunk_len < 20) + return false; + size_remaining = chunk_len - 8; + stream_read_uint32(&ctx->stream); /* version + flags */ + size_remaining -= 4; + + default_size = stream_read_uint32(&ctx->stream); + size_remaining -= 4; + + numsizes = stream_read_uint32(&ctx->stream); + size_remaining -= 4; + + if (default_size == 0 && numsizes > size_remaining / 4) + return false; + ctx->res->num_samples = numsizes; + + if (default_size != 0) + { + /* All samples same size */ + uint32_t cap = ctx->res->sample_sizes == NULL ? 0 : + MIN(numsizes, ctx->res->sample_sizes_cap); + for (i = 0; i < cap; i++) + ctx->res->sample_sizes[i] = default_size; + + if (size_remaining > 0) + stream_skip(&ctx->stream, size_remaining); + return true; + } + + /* Variable sample sizes */ + { + uint32_t cap = ctx->res->sample_sizes == NULL ? 0 : + MIN(numsizes, ctx->res->sample_sizes_cap); + if (!stream_read_uint32_array(&ctx->stream, + ctx->res->sample_sizes, cap)) + return false; + size_remaining -= (size_t)cap * 4; + /* Skip remaining if we hit our cap */ + if (numsizes > cap) + { + size_t skip = (size_t)(numsizes - cap) * 4; + + stream_skip(&ctx->stream, skip); + size_remaining -= skip; + } + } + + if (size_remaining > 0) + stream_skip(&ctx->stream, size_remaining); + + return true; +} + +/* stsc - sample-to-chunk table */ +static bool read_chunk_stsc(struct mp4_parse_ctx *ctx, size_t chunk_len) +{ + size_t size_remaining; + uint32_t numentries, i, fill; + + if (chunk_len < 16) + return false; + size_remaining = chunk_len - 8; + stream_read_uint32(&ctx->stream); /* version + flags */ + size_remaining -= 4; + + numentries = stream_read_uint32(&ctx->stream); + size_remaining -= 4; + + if (numentries > size_remaining / 12) + return false; + ctx->res->num_stsc = numentries; + fill = ctx->res->stsc == NULL ? 0 : + MIN(numentries, ctx->res->stsc_cap); + + if (!stream_read(&ctx->stream, ctx->res->stsc, + (size_t)fill * sizeof(ctx->res->stsc[0]))) + return false; + size_remaining -= (size_t)fill * 12; + for (i = 0; i < fill; i++) + { + ctx->res->stsc[i].first_chunk = + betoh32(ctx->res->stsc[i].first_chunk); + ctx->res->stsc[i].samples_per_chunk = + betoh32(ctx->res->stsc[i].samples_per_chunk); + ctx->res->stsc[i].sample_desc_index = + betoh32(ctx->res->stsc[i].sample_desc_index); + } + + if (numentries > fill) + { + size_t skip = (size_t)(numentries - fill) * 12; + + stream_skip(&ctx->stream, skip); + size_remaining -= skip; + } + + if (size_remaining > 0) + stream_skip(&ctx->stream, size_remaining); + + return true; +} + +/* stco - chunk offset table */ +static bool read_chunk_stco(struct mp4_parse_ctx *ctx, size_t chunk_len) +{ + size_t size_remaining; + uint32_t numentries, cap; + + if (chunk_len < 16) + return false; + size_remaining = chunk_len - 8; + stream_read_uint32(&ctx->stream); /* version + flags */ + size_remaining -= 4; + + numentries = stream_read_uint32(&ctx->stream); + size_remaining -= 4; + + if (numentries > size_remaining / 4) + return false; + ctx->res->num_stco = numentries; /* real count from file */ + cap = ctx->res->chunk_offsets == NULL ? 0 : + MIN(numentries, ctx->res->chunk_offsets_cap); + + if (!stream_read_uint32_array(&ctx->stream, + ctx->res->chunk_offsets, cap)) + return false; + size_remaining -= (size_t)cap * 4; + + if (numentries > cap) + { + size_t skip = (size_t)(numentries - cap) * 4; + + stream_skip(&ctx->stream, skip); + size_remaining -= skip; + } + + if (size_remaining > 0) + stream_skip(&ctx->stream, size_remaining); + + return true; +} + +/* co64 - 64-bit chunk offset table (for files >4GB). + * Reads 64-bit offsets but truncates to 32-bit. + * Files with chunk offsets >4GB are not supported. */ +static bool read_chunk_co64(struct mp4_parse_ctx *ctx, size_t chunk_len) +{ + size_t size_remaining; + uint32_t numentries, i, cap; + + if (chunk_len < 16) + return false; + size_remaining = chunk_len - 8; + stream_read_uint32(&ctx->stream); /* version + flags */ + size_remaining -= 4; + + numentries = stream_read_uint32(&ctx->stream); + size_remaining -= 4; + + if (numentries > size_remaining / 8) + return false; + ctx->res->num_stco = numentries; /* real count from file */ + cap = ctx->res->chunk_offsets == NULL ? 0 : + MIN(numentries, ctx->res->chunk_offsets_cap); + + for (i = 0; i < cap; i++) + { + uint32_t hi = stream_read_uint32(&ctx->stream); + uint32_t lo = stream_read_uint32(&ctx->stream); + if (hi != 0) + return false; + ctx->res->chunk_offsets[i] = lo; + size_remaining -= 8; + } + + if (numentries > cap) + { + size_t skip = (size_t)(numentries - cap) * 8; + + stream_skip(&ctx->stream, skip); + size_remaining -= skip; + } + + if (size_remaining > 0) + stream_skip(&ctx->stream, size_remaining); + + return true; +} + +/* stss - sync sample (keyframe) table */ +static bool read_chunk_stss(struct mp4_parse_ctx *ctx, size_t chunk_len) +{ + size_t size_remaining; + uint32_t numentries; + uint32_t cap; + + if (chunk_len < 16) + return false; + size_remaining = chunk_len - 8; + stream_read_uint32(&ctx->stream); /* version + flags */ + size_remaining -= 4; + + numentries = stream_read_uint32(&ctx->stream); + size_remaining -= 4; + + if (numentries > size_remaining / 4) + return false; + ctx->res->num_stss = numentries; + cap = ctx->res->sync_samples == NULL ? 0 : + MIN(numentries, ctx->res->sync_samples_cap); + + if (!stream_read_uint32_array(&ctx->stream, + ctx->res->sync_samples, cap)) + return false; + size_remaining -= (size_t)cap * 4; + + if (numentries > cap) + { + size_t skip = (size_t)(numentries - cap) * 4; + + stream_skip(&ctx->stream, skip); + size_remaining -= skip; + } + + if (size_remaining > 0) + stream_skip(&ctx->stream, size_remaining); + + return true; +} + +/* stbl - sample table container */ +static bool read_chunk_stbl(struct mp4_parse_ctx *ctx, size_t chunk_len) +{ + size_t size_remaining = chunk_len - 8; + + while (size_remaining >= 8) + { + uint32_t sub_len = stream_read_uint32(&ctx->stream); + uint32_t sub_id = stream_read_uint32(&ctx->stream); + + if (sub_len < 8 || sub_len > size_remaining) + return false; + + switch (sub_id) + { + case MAKEFOURCC('s','t','s','d'): + if (!read_chunk_stsd_video(ctx, sub_len)) + return false; + break; + case MAKEFOURCC('s','t','t','s'): + if (!read_chunk_stts(ctx, sub_len)) + return false; + break; + case MAKEFOURCC('s','t','s','z'): + if (!read_chunk_stsz(ctx, sub_len)) + return false; + break; + case MAKEFOURCC('s','t','s','c'): + if (!read_chunk_stsc(ctx, sub_len)) + return false; + break; + case MAKEFOURCC('s','t','c','o'): + if (!read_chunk_stco(ctx, sub_len)) + return false; + break; + case MAKEFOURCC('c','o','6','4'): + if (!read_chunk_co64(ctx, sub_len)) + return false; + break; + case MAKEFOURCC('s','t','s','s'): + if (!read_chunk_stss(ctx, sub_len)) + return false; + break; + default: + stream_skip(&ctx->stream, sub_len - 8); + break; + } + + size_remaining -= sub_len; + } + + return !ctx->stream.eof && size_remaining == 0; +} + +/* minf - accepts vmhd (video) instead of smhd (audio) */ +static bool read_chunk_minf(struct mp4_parse_ctx *ctx, size_t chunk_len) +{ + size_t size_remaining = chunk_len - 8; + + /* Walk sub-boxes, look for vmhd to confirm video, then find stbl */ + while (size_remaining >= 8) + { + uint32_t sub_len = stream_read_uint32(&ctx->stream); + uint32_t sub_id = stream_read_uint32(&ctx->stream); + + if (sub_len < 8 || sub_len > size_remaining) + return false; + + switch (sub_id) + { + case MAKEFOURCC('v','m','h','d'): + /* Video media header confirms this is a video track */ + ctx->in_video_trak = true; + stream_skip(&ctx->stream, sub_len - 8); + break; + case MAKEFOURCC('s','m','h','d'): + /* Sound media header confirms this is an audio track */ + ctx->in_video_trak = false; + ctx->in_audio_trak = true; + stream_skip(&ctx->stream, sub_len - 8); + break; + case MAKEFOURCC('s','t','b','l'): + if (ctx->in_video_trak) + { + if (!read_chunk_stbl(ctx, sub_len)) + return false; + } + else if (ctx->in_audio_trak) + { + if (!read_chunk_stbl_audio(ctx, sub_len)) + return false; + } + else + { + stream_skip(&ctx->stream, sub_len - 8); + } + break; + default: + stream_skip(&ctx->stream, sub_len - 8); + break; + } + + size_remaining -= sub_len; + } + + return !ctx->stream.eof && size_remaining == 0; +} + +/* mdhd - media header (extract timescale) */ +static bool read_chunk_mdhd(struct mp4_parse_ctx *ctx, size_t chunk_len) +{ + size_t size_remaining; + uint32_t version; + + if (chunk_len < 28) + return false; + size_remaining = chunk_len - 8; + version = stream_read_uint32(&ctx->stream); /* version + flags */ + size_remaining -= 4; + + { + uint32_t ts; + + if ((version >> 24) == 0) + { + /* Version 0: 4-byte fields */ + stream_skip(&ctx->stream, 4); /* creation_time */ + stream_skip(&ctx->stream, 4); /* modification_time */ + ts = stream_read_uint32(&ctx->stream); + stream_skip(&ctx->stream, 4); /* duration */ + size_remaining -= 16; + } + else if ((version >> 24) == 1) + { + if (chunk_len < 40) + return false; + /* Version 1: 8-byte fields */ + stream_skip(&ctx->stream, 8); /* creation_time */ + stream_skip(&ctx->stream, 8); /* modification_time */ + ts = stream_read_uint32(&ctx->stream); + stream_skip(&ctx->stream, 8); /* duration */ + size_remaining -= 28; + } + else + { + return false; + } + + /* Save pending; commit at end of trak once type is known. + * mdhd comes before hdlr in mdia, so track type isn't known yet. */ + ctx->pending_timescale = ts; + } + + if (size_remaining > 0) + stream_skip(&ctx->stream, size_remaining); + + return true; +} + +/* hdlr - handler reference (check for 'vide') */ +static bool read_chunk_hdlr(struct mp4_parse_ctx *ctx, size_t chunk_len) +{ + size_t size_remaining; + uint32_t handler_type; + + if (chunk_len < 20) + return false; + size_remaining = chunk_len - 8; + stream_read_uint32(&ctx->stream); /* version + flags */ + stream_read_uint32(&ctx->stream); /* pre_defined */ + handler_type = stream_read_uint32(&ctx->stream); + size_remaining -= 12; + + ctx->in_video_trak = false; + ctx->in_audio_trak = false; + + if (handler_type == MAKEFOURCC('v','i','d','e')) + ctx->in_video_trak = true; + else if (handler_type == MAKEFOURCC('s','o','u','n')) + ctx->in_audio_trak = true; + + if (size_remaining > 0) + stream_skip(&ctx->stream, size_remaining); + + return true; +} + +/* mdia - media container */ +static bool read_chunk_mdia(struct mp4_parse_ctx *ctx, size_t chunk_len) +{ + size_t size_remaining = chunk_len - 8; + + while (size_remaining >= 8) + { + uint32_t sub_len = stream_read_uint32(&ctx->stream); + uint32_t sub_id = stream_read_uint32(&ctx->stream); + + if (sub_len < 8 || sub_len > size_remaining) + return false; + + switch (sub_id) + { + case MAKEFOURCC('m','d','h','d'): + if (!read_chunk_mdhd(ctx, sub_len)) + return false; + break; + case MAKEFOURCC('h','d','l','r'): + if (!read_chunk_hdlr(ctx, sub_len)) + return false; + break; + case MAKEFOURCC('m','i','n','f'): + if (!read_chunk_minf(ctx, sub_len)) + return false; + break; + default: + stream_skip(&ctx->stream, sub_len - 8); + break; + } + + size_remaining -= sub_len; + } + + return !ctx->stream.eof && size_remaining == 0; +} + +/* tkhd - track header (extract display width/height for PAR) */ +static bool read_chunk_tkhd(struct mp4_parse_ctx *ctx, size_t chunk_len) +{ + size_t size_remaining; + uint32_t version; + uint32_t dw_fp, dh_fp; /* 16.16 fixed-point display dimensions */ + + if (chunk_len < 92) + return false; + size_remaining = chunk_len - 8; + version = stream_read_uint32(&ctx->stream); /* version + flags */ + size_remaining -= 4; + + if ((version >> 24) == 0) + { + /* Version 0: creation(4)+modification(4)+track_id(4)+reserved(4)+ + duration(4) = 20 bytes, then reserved(8)+layer(2)+alt_group(2)+ + volume(2)+reserved(2)+matrix(36)+width(4)+height(4) = 56 bytes */ + stream_skip(&ctx->stream, 20 + 8 + 2 + 2 + 2 + 2 + 36); + size_remaining -= 20 + 8 + 2 + 2 + 2 + 2 + 36; + } + else if ((version >> 24) == 1) + { + if (chunk_len < 104) + return false; + /* Version 1: creation(8)+modification(8)+track_id(4)+reserved(4)+ + duration(8) = 32 bytes */ + stream_skip(&ctx->stream, 32 + 8 + 2 + 2 + 2 + 2 + 36); + size_remaining -= 32 + 8 + 2 + 2 + 2 + 2 + 36; + } + else + { + return false; + } + + dw_fp = stream_read_uint32(&ctx->stream); /* width in 16.16 */ + dh_fp = stream_read_uint32(&ctx->stream); /* height in 16.16 */ + size_remaining -= 8; + + /* Commit to res only after mdia confirms video */ + ctx->tkhd_dw = (uint16_t)(dw_fp >> 16); + ctx->tkhd_dh = (uint16_t)(dh_fp >> 16); + + if (size_remaining > 0) + stream_skip(&ctx->stream, size_remaining); + + return true; +} + +/* edts/elst - edit list (extract media_time for encoder priming offset) */ +static bool read_chunk_elst(struct mp4_parse_ctx *ctx, size_t chunk_len) +{ + size_t size_remaining; + uint32_t version_flags; + uint32_t entry_count, i; + + if (chunk_len < 16) + return false; + size_remaining = chunk_len - 8; + version_flags = stream_read_uint32(&ctx->stream); + size_remaining -= 4; + + entry_count = stream_read_uint32(&ctx->stream); + size_remaining -= 4; + + { + size_t entry_size = ((version_flags >> 24) == 0) ? 12 : 20; + + if ((version_flags >> 24) > 1 || + entry_count > size_remaining / entry_size) + return false; + for (i = 0; i < entry_count; i++) + { + int32_t media_time; + + if ((version_flags >> 24) == 0) + { + stream_skip(&ctx->stream, 4); /* segment_duration */ + media_time = (int32_t)stream_read_uint32(&ctx->stream); + stream_skip(&ctx->stream, 4); /* media_rate (16.16) */ + size_remaining -= 12; + } + else + { + stream_skip(&ctx->stream, 8); /* segment_duration (64-bit) */ + stream_skip(&ctx->stream, 4); /* media_time high */ + media_time = (int32_t)stream_read_uint32(&ctx->stream); + stream_skip(&ctx->stream, 4); /* media_rate */ + size_remaining -= 20; + } + + /* First non-empty edit: media_time > 0 = priming offset. + * Save until the track type is known. */ + if (i == 0 && media_time > 0) + ctx->pending_lead_trim = (uint32_t)media_time; + } + } + + if (size_remaining > 0) + stream_skip(&ctx->stream, size_remaining); + + return true; +} + +/* edts container: walk for elst */ +static bool read_chunk_edts(struct mp4_parse_ctx *ctx, size_t chunk_len) +{ + size_t size_remaining = chunk_len - 8; + + while (size_remaining >= 8) + { + uint32_t sub_len = stream_read_uint32(&ctx->stream); + uint32_t sub_id = stream_read_uint32(&ctx->stream); + + if (sub_len < 8 || sub_len > size_remaining) + return false; + + if (sub_id == MAKEFOURCC('e','l','s','t')) + { + if (!read_chunk_elst(ctx, sub_len)) + return false; + } + else + { + stream_skip(&ctx->stream, sub_len - 8); + } + + size_remaining -= sub_len; + } + + return !ctx->stream.eof && size_remaining == 0; +} + +/* trak - track container */ +static bool read_chunk_trak(struct mp4_parse_ctx *ctx, size_t chunk_len) +{ + size_t size_remaining = chunk_len - 8; + + /* Reset per-track state (pending fields MUST be reset to prevent + * stale values from one trak leaking into the next) */ + ctx->in_video_trak = false; + ctx->in_audio_trak = false; + ctx->pending_timescale = 0; + ctx->pending_lead_trim = 0; + ctx->tkhd_dw = 0; + ctx->tkhd_dh = 0; + + while (size_remaining >= 8) + { + uint32_t sub_len = stream_read_uint32(&ctx->stream); + uint32_t sub_id = stream_read_uint32(&ctx->stream); + + if (sub_len < 8 || sub_len > size_remaining) + return false; + + switch (sub_id) + { + case MAKEFOURCC('t','k','h','d'): + if (!read_chunk_tkhd(ctx, sub_len)) + return false; + break; + case MAKEFOURCC('m','d','i','a'): + if (!read_chunk_mdia(ctx, sub_len)) + return false; + break; + case MAKEFOURCC('e','d','t','s'): + /* edts comes before mdia, so the track type is unknown. + * Parse unconditionally, save to pending_lead_trim. + * Committed to audio_lead_trim at end of trak. */ + if (!read_chunk_edts(ctx, sub_len)) + return false; + break; + default: + stream_skip(&ctx->stream, sub_len - 8); + break; + } + + size_remaining -= sub_len; + } + + /* Commit pending per-trak fields now that track type is known. + * mdhd timescale and edts/elst are parsed before hdlr identifies + * the track type, so they were saved to pending fields above. */ + if (ctx->in_video_trak && ctx->res->format != 0) + { + ctx->found_video = true; + ctx->res->display_width = ctx->tkhd_dw; + ctx->res->display_height = ctx->tkhd_dh; + ctx->res->timescale = ctx->pending_timescale; + } + + if (ctx->in_audio_trak && ctx->res->audio_format != 0) + { + ctx->found_audio = true; + ctx->res->audio_lead_trim = ctx->pending_lead_trim; + } + + return !ctx->stream.eof && size_remaining == 0; +} + +/* moov - movie container */ +static bool read_chunk_moov(struct mp4_parse_ctx *ctx, size_t chunk_len) +{ + size_t size_remaining = chunk_len - 8; + + while (size_remaining >= 8) + { + uint32_t sub_len = stream_read_uint32(&ctx->stream); + uint32_t sub_id = stream_read_uint32(&ctx->stream); + + if (sub_len < 8 || sub_len > size_remaining) + return false; + + switch (sub_id) + { + case MAKEFOURCC('t','r','a','k'): + /* Parse until we have both video and audio (or all traks) */ + if (!ctx->found_video || !ctx->found_audio) + { + if (!read_chunk_trak(ctx, sub_len)) + return false; + } + else + { + stream_skip(&ctx->stream, sub_len - 8); + } + break; + default: + stream_skip(&ctx->stream, sub_len - 8); + break; + } + + size_remaining -= sub_len; + } + + return !ctx->stream.eof && size_remaining == 0; +} + +int mp4v_demux_open(const char *filepath, + struct mp4v_demux_res *res, + uint32_t *sample_buf, uint32_t sample_cap, + uint32_t *chunk_buf, uint32_t chunk_cap, + struct mp4v_stsc_entry *stsc_buf, uint32_t stsc_cap, + uint32_t *sync_buf, uint32_t sync_cap) +{ + struct mp4_parse_ctx ctx; + off_t file_size; + int fd; + + if (filepath == NULL || res == NULL) + return -1; + fd = rb->open(filepath, O_RDONLY); + if (fd < 0) + return -1; + file_size = rb->ffilesize(fd); + if (file_size <= 0 || (uint64_t)file_size > UINT32_MAX) + { + rb->close(fd); + return -1; + } + + rb->memset(res, 0, sizeof(*res)); + res->sample_sizes = sample_buf; + res->sample_sizes_cap = sample_cap; + res->chunk_offsets = chunk_buf; + res->chunk_offsets_cap = chunk_cap; + res->stsc = stsc_buf; + res->stsc_cap = stsc_cap; + res->sync_samples = sync_buf; + res->sync_samples_cap = sync_cap; + res->file_size = (uint32_t)file_size; + + rb->memset(&ctx, 0, sizeof(ctx)); + stream_init(&ctx.stream, fd, file_size); + ctx.res = res; + + /* Walk top-level boxes */ + while (ctx.stream.pos < file_size && !ctx.stream.eof) + { + uint32_t chunk_len = stream_read_uint32(&ctx.stream); + uint32_t chunk_id; + + if (ctx.stream.eof) + break; + + if (chunk_len < 8) + { + rb->close(fd); + return -1; + } + + chunk_id = stream_read_uint32(&ctx.stream); + if (ctx.stream.eof || + chunk_len - 8 > (uint32_t)(file_size - ctx.stream.pos)) + { + rb->close(fd); + return -1; + } + + switch (chunk_id) + { + case MAKEFOURCC('f','t','y','p'): + stream_skip(&ctx.stream, chunk_len - 8); + break; + case MAKEFOURCC('m','o','o','v'): + if (!read_chunk_moov(&ctx, chunk_len)) + { + rb->close(fd); + return -1; + } + break; + case MAKEFOURCC('m','d','a','t'): + if (chunk_len > 8) + { + res->mdat_offset = (uint32_t)stream_tell(&ctx.stream); + res->mdat_len = chunk_len - 8; + } + /* If we already found everything we need, we're done */ + if (ctx.found_video && ctx.found_audio) + goto done; + stream_skip(&ctx.stream, chunk_len - 8); + break; + default: + stream_skip(&ctx.stream, chunk_len - 8); + break; + } + } + +done: + rb->close(fd); + + if (ctx.stream.eof || !ctx.found_video || res->format == 0) + return -1; + + return 0; +} + +int mp4v_get_sample_offset(const struct mp4v_demux_res *res, + uint32_t sample_index, + uint32_t *offset_out, + uint32_t *size_out) +{ + uint32_t chunk_index = 0; + uint32_t sample_in_chunk = 0; + uint64_t samples_so_far = 0; + uint64_t chunk_offset; + uint32_t i; + bool found = false; + + if (res == NULL || offset_out == NULL || size_out == NULL || + res->sample_sizes == NULL || res->chunk_offsets == NULL || + res->stsc == NULL || sample_index >= res->num_samples || + sample_index >= res->sample_sizes_cap || + res->num_stsc > res->stsc_cap || + res->num_stco > res->chunk_offsets_cap) + return -1; + + *size_out = res->sample_sizes[sample_index]; + + /* Walk stsc to find which chunk contains this sample */ + for (i = 0; i < res->num_stsc; i++) + { + uint32_t first_chunk; + uint32_t spc = res->stsc[i].samples_per_chunk; + uint32_t next_first; + + if (res->stsc[i].first_chunk == 0 || spc == 0) + return -1; + first_chunk = res->stsc[i].first_chunk - 1; + if (i + 1 < res->num_stsc) + { + if (res->stsc[i + 1].first_chunk == 0) + return -1; + next_first = res->stsc[i + 1].first_chunk - 1; + } + else + next_first = res->num_stco; + + if (next_first <= first_chunk || next_first > res->num_stco) + return -1; + uint32_t chunks_in_run = next_first - first_chunk; + uint64_t samples_in_run = (uint64_t)chunks_in_run * spc; + + if (samples_so_far + samples_in_run > sample_index) + { + /* Sample is within this stsc run */ + uint32_t sample_offset_in_run = sample_index - samples_so_far; + chunk_index = first_chunk + (sample_offset_in_run / spc); + sample_in_chunk = sample_offset_in_run % spc; + found = true; + break; + } + + samples_so_far += samples_in_run; + } + + if (!found || chunk_index >= res->num_stco) + return -1; + + /* Get the chunk's file offset */ + chunk_offset = res->chunk_offsets[chunk_index]; + + /* Add offsets for preceding samples within this chunk */ + { + uint32_t first_sample_in_chunk = sample_index - sample_in_chunk; + uint32_t j; + + for (j = 0; j < sample_in_chunk; j++) + { + uint32_t index = first_sample_in_chunk + j; + + if (index >= res->num_samples || index >= res->sample_sizes_cap) + return -1; + chunk_offset += res->sample_sizes[index]; + } + } + + if (chunk_offset > UINT32_MAX || + chunk_offset + *size_out > res->file_size) + return -1; + *offset_out = (uint32_t)chunk_offset; + return 0; +} + +bool mp4v_is_keyframe(const struct mp4v_demux_res *res, + uint32_t sample_index) +{ + uint32_t i; + uint32_t sample_num = sample_index + 1; /* stss is 1-based */ + + /* No stss table means all samples are sync samples */ + if (res->num_stss == 0) + return true; + if (res->sync_samples == NULL || + res->sync_samples_cap < res->num_stss) + return false; + + for (i = 0; i < res->num_stss; i++) + { + if (res->sync_samples[i] == sample_num) + return true; + if (res->sync_samples[i] > sample_num) + break; + } + + return false; +} diff --git a/apps/plugins/h264_player/mp4_demux.h b/apps/plugins/h264_player/mp4_demux.h new file mode 100644 index 0000000000..33d2e02a4d --- /dev/null +++ b/apps/plugins/h264_player/mp4_demux.h @@ -0,0 +1,150 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2025-2026 David Cormier + * + * H.264 player MP4 demuxer - extracts video track data from MP4/M4V files. + * Forked from libm4a/demux.c, adapted for fd-based I/O and video tracks. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#ifndef MP4_DEMUX_H +#define MP4_DEMUX_H + +#include +#include +#include + +/* Max SPS/PPS codec data from avcC box */ +#define MP4V_MAX_CODECDATA 512 + +#define MP4V_MAX_STTS 4096 + +#ifndef MAKEFOURCC +#define MAKEFOURCC(ch0, ch1, ch2, ch3) ( \ + ( (int32_t)(char)(ch0) << 24 ) | \ + ( (int32_t)(char)(ch1) << 16 ) | \ + ( (int32_t)(char)(ch2) << 8 ) | \ + ( (int32_t)(char)(ch3) ) ) +#endif + +struct mp4v_stts_entry { + uint32_t sample_count; + uint32_t sample_delta; +}; + +struct mp4v_stsc_entry { + uint32_t first_chunk; + uint32_t samples_per_chunk; + uint32_t sample_desc_index; +}; + +struct mp4v_demux_res { + /* Video format */ + uint32_t format; /* fourcc: 'avc1' etc. */ + uint16_t width; /* coded width from avc1 sample entry */ + uint16_t height; /* coded height from avc1 sample entry */ + uint16_t display_width; /* display width from tkhd (PAR-adjusted) */ + uint16_t display_height; /* display height from tkhd */ + uint32_t timescale; /* from mdhd */ + + /* avcC decoder config (SPS + PPS) */ + uint8_t avc_profile; + uint8_t avc_level; + uint8_t nalu_len_size; /* length field size (1-4 bytes, typically 4) */ + uint8_t num_sps; + uint8_t num_pps; + uint32_t codecdata_len; + uint8_t codecdata[MP4V_MAX_CODECDATA]; /* raw avcC data */ + + /* Sample sizes (stsz) */ + uint32_t num_samples; + uint32_t *sample_sizes; /* caller-provided buffer */ + uint32_t sample_sizes_cap; + + /* Time-to-sample (stts) */ + uint32_t num_stts; + struct mp4v_stts_entry stts[MP4V_MAX_STTS]; + + /* Sample-to-chunk (stsc) */ + uint32_t num_stsc; + struct mp4v_stsc_entry *stsc; /* caller-provided buffer */ + uint32_t stsc_cap; + + /* Chunk offsets (stco) */ + uint32_t num_stco; + uint32_t *chunk_offsets; /* caller-provided buffer */ + uint32_t chunk_offsets_cap; + + /* Sync samples / keyframes (stss) */ + uint32_t num_stss; + uint32_t *sync_samples; /* caller-provided buffer */ + uint32_t sync_samples_cap; + + /* File and mdat location */ + uint32_t file_size; + uint32_t mdat_offset; + uint32_t mdat_len; + + /* Audio track (populated when mp4a trak found) */ + uint32_t audio_format; /* fourcc: 'mp4a' or 0 if no audio */ + uint32_t audio_sample_rate; /* from mp4a sample entry */ + uint8_t audio_codecdata[64]; /* AudioSpecificConfig from esds */ + uint32_t audio_codecdata_len; + uint32_t audio_lead_trim; /* encoder priming from edts/elst */ + + /* Audio table counts used to size aac.codec's workspace. The AAC codec + * parses its own table data from the file. */ + uint32_t audio_num_samples; + uint32_t audio_num_stsc; + uint32_t audio_num_stco; +}; + +/* Open and parse an MP4 file, extracting video and audio tracks. + * + * filepath: path to the .mp4/.m4v file + * res: output struct (caller allocates) + * sample_buf: buffer for video sample_sizes array + * sample_cap: number of uint32_t entries in sample_buf + * chunk_buf: buffer for video chunk_offsets array + * chunk_cap: number of uint32_t entries in chunk_buf + * stsc_buf: buffer for video sample-to-chunk entries + * stsc_cap: number of entries in stsc_buf + * sync_buf: buffer for video sync-sample entries + * sync_cap: number of entries in sync_buf + * + * Returns 0 on success (video found), -1 on error. + * Audio track is optional; check res->audio_format != 0. */ +int mp4v_demux_open(const char *filepath, + struct mp4v_demux_res *res, + uint32_t *sample_buf, uint32_t sample_cap, + uint32_t *chunk_buf, uint32_t chunk_cap, + struct mp4v_stsc_entry *stsc_buf, uint32_t stsc_cap, + uint32_t *sync_buf, uint32_t sync_cap); + +/* Get the file offset and size of video sample N (0-based). + * Returns 0 on success, -1 on error. */ +int mp4v_get_sample_offset(const struct mp4v_demux_res *res, + uint32_t sample_index, + uint32_t *offset_out, + uint32_t *size_out); + +/* Check if video sample N is a keyframe (sync sample). + * Returns true if it's a keyframe or if no stss table exists + * (meaning all samples are sync samples). */ +bool mp4v_is_keyframe(const struct mp4v_demux_res *res, + uint32_t sample_index); + +#endif /* MP4_DEMUX_H */ diff --git a/apps/plugins/h264_player/video_audio.c b/apps/plugins/h264_player/video_audio.c new file mode 100644 index 0000000000..dd6a786086 --- /dev/null +++ b/apps/plugins/h264_player/video_audio.c @@ -0,0 +1,572 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2025-2026 David Cormier + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +/* + * AAC audio service for the native S5L8702 H.264 player. + * + * The decoder itself remains Rockbox's normal dynamically-loaded aac.codec. + * That is important on iPod 6G: FAAD's hot code and working state are an IRAM + * overlay and cannot be linked into the permanent core without overflowing + * IRAM. This file borrows the existing codec thread, supplies file-buffer + * callbacks for the MP4, and sends decoded PCM to video_pcm. + */ + +#include "plugin.h" +#include + +#include "codec_thread.h" +#include "codecs.h" +#include "file.h" +#include "kernel.h" +#include "metadata.h" +#include "mp4_demux.h" +#include "video_audio.h" +#include "video_pcm.h" + +#include +#include + +#define AUDIO_FILE_BUFFER_SIZE 8192 +#define AUDIO_PCM_FRAMES 1024 +#define AUDIO_START_BUFFER_MS 1000 +/* libm4a's sample_offset_t stores a uint32_t sample and file offset. Keep a + * normal CODEC_SIZE of headroom after the complete Apple chunk map. */ +#define AUDIO_CHUNK_LOOKUP_ENTRY_SIZE (2u * sizeof(uint32_t)) + +static struct codec_api video_ci; +static struct mp3entry video_id3; +static int audio_fd = -1; +static off_t audio_file_size; +static uint8_t audio_file_buffer[AUDIO_FILE_BUFFER_SIZE]; +static off_t audio_file_buffer_offset; +static size_t audio_file_buffer_length; +static int16_t audio_pcm[AUDIO_PCM_FRAMES * 2]; +static void *audio_codec_workspace; +static size_t audio_codec_workspace_size; +static uint32_t audio_sample_rate; +static int audio_sample_depth; +static int audio_stereo_mode; +static volatile long audio_action; +static volatile uint32_t audio_seek_target_ms; +static volatile bool audio_paused; +static volatile bool audio_is_ready; +static volatile bool audio_is_playing; +static volatile bool audio_has_error; +static bool audio_initialized; + +size_t video_audio_workspace_size(const struct mp4v_demux_res *demux) +{ + size_t table_size; + + if (demux == NULL || demux->audio_num_samples == 0 || + demux->audio_num_stco == 0 || + demux->audio_num_stco > + (SIZE_MAX - CODEC_SIZE) / AUDIO_CHUNK_LOOKUP_ENTRY_SIZE) + return 0; + table_size = (size_t)demux->audio_num_stco * + AUDIO_CHUNK_LOOKUP_ENTRY_SIZE; + return CODEC_SIZE + table_size; +} + +static int16_t audio_clip16(int32_t value) +{ + if (value > 32767) + return 32767; + if (value < -32768) + return -32768; + return (int16_t)value; +} + +static void *audio_codec_get_buffer(size_t *size) +{ + *size = audio_codec_workspace_size; + return audio_codec_workspace; +} + +static bool audio_refill(off_t position) +{ + ssize_t count; + + if (position < 0 || position > audio_file_size || + rb->lseek(audio_fd, position, SEEK_SET) < 0) + return false; + count = rb->read(audio_fd, audio_file_buffer, sizeof(audio_file_buffer)); + if (count < 0) + return false; + audio_file_buffer_offset = position; + audio_file_buffer_length = (size_t)count; + return true; +} + +static size_t audio_read_filebuf(void *destination, size_t size) +{ + size_t done = 0; + + while (done < size && video_ci.curpos < audio_file_size) + { + size_t in_buffer; + size_t chunk; + + if (video_ci.curpos < audio_file_buffer_offset || + video_ci.curpos >= audio_file_buffer_offset + + (off_t)audio_file_buffer_length) + { + if (!audio_refill(video_ci.curpos)) + break; + } + in_buffer = (size_t)(video_ci.curpos - audio_file_buffer_offset); + chunk = audio_file_buffer_length - in_buffer; + if (chunk > size - done) + chunk = size - done; + if (chunk == 0) + break; + rb->memcpy((uint8_t *)destination + done, + audio_file_buffer + in_buffer, chunk); + video_ci.curpos += chunk; + done += chunk; + } + return done; +} + +static void *audio_request_buffer(size_t *real_size, size_t requested) +{ + size_t available; + size_t in_buffer; + size_t needed; + off_t remaining; + + if (video_ci.curpos >= audio_file_size) + { + *real_size = 0; + return audio_file_buffer; + } + + remaining = audio_file_size - video_ci.curpos; + needed = requested; + if ((off_t)needed > remaining) + needed = (size_t)remaining; + + if (video_ci.curpos < audio_file_buffer_offset || + video_ci.curpos >= audio_file_buffer_offset + + (off_t)audio_file_buffer_length) + { + if (!audio_refill(video_ci.curpos)) + { + *real_size = 0; + return audio_file_buffer; + } + } + in_buffer = (size_t)(video_ci.curpos - audio_file_buffer_offset); + available = audio_file_buffer_length - in_buffer; + + /* codec_api.request_buffer promises the requested span whenever that + * many file bytes remain. Do not return the short tail of our 8 KiB + * cache: FAAD treats a truncated AAC frame as a fatal decode error. */ + if (available < needed) + { + if (!audio_refill(video_ci.curpos)) + { + *real_size = 0; + return audio_file_buffer; + } + in_buffer = 0; + available = audio_file_buffer_length; + } + + if (available > needed) + available = needed; + *real_size = available; + return audio_file_buffer + in_buffer; +} + +static void audio_advance_buffer(size_t amount) +{ + off_t remaining = audio_file_size - video_ci.curpos; + + if ((off_t)amount > remaining) + amount = remaining > 0 ? (size_t)remaining : 0; + video_ci.curpos += amount; + video_id3.offset = video_ci.curpos; +} + +static bool audio_seek_buffer(size_t position) +{ + if ((off_t)position > audio_file_size) + return false; + video_ci.curpos = position; + video_id3.offset = position; + return true; +} + +static void audio_seek_complete(void) +{ +} + +static void audio_set_elapsed(unsigned long elapsed) +{ + video_id3.elapsed = elapsed; +} + +static void audio_set_offset(size_t offset) +{ + video_id3.offset = offset; +} + +static void audio_configure(int setting, intptr_t value) +{ + switch (setting) + { + case DSP_SET_FREQUENCY: + if (value > 0) + audio_sample_rate = (uint32_t)value; + break; + case DSP_SET_SAMPLE_DEPTH: + audio_sample_depth = (int)value; + break; + case DSP_SET_STEREO_MODE: + audio_stereo_mode = (int)value; + break; + default: + break; + } +} + +static long audio_get_command(intptr_t *parameter) +{ + long action; + + while (audio_paused && audio_action == CODEC_ACTION_NULL) + rb->sleep(1); + rb->yield(); + action = audio_action; + if (action == CODEC_ACTION_SEEK_TIME) + { + if (parameter != NULL) + *parameter = (intptr_t)audio_seek_target_ms; + /* A seek is a one-shot command. HALT deliberately remains latched + * until codec teardown so the producer cannot resume behind us. */ + audio_action = CODEC_ACTION_NULL; + } + return action; +} + +static bool audio_loop_track(void) +{ + return false; +} + +static void audio_strip_filesize(off_t size) +{ + video_ci.filesize = size; +} + +static int32_t audio_sample_value(const void *channel, int index) +{ + if (audio_sample_depth <= 16) + return ((const int16_t *)channel)[index]; + + { + int shift = audio_sample_depth + 1 - 16; + int32_t value = ((const int32_t *)channel)[index]; + int32_t bias = shift > 0 ? (int32_t)1 << (shift - 1) : 0; + + return shift > 0 ? (value + bias) >> shift : value; + } +} + +static void audio_pcm_insert(const void *channel1, const void *channel2, + int count) +{ + int consumed = 0; + + while (consumed < count && audio_action != CODEC_ACTION_HALT) + { + int frames; + int written = 0; + int i; + + while (audio_paused && audio_action != CODEC_ACTION_HALT) + rb->sleep(1); + if (audio_action == CODEC_ACTION_HALT) + break; + + frames = count - consumed; + if (frames > AUDIO_PCM_FRAMES) + frames = AUDIO_PCM_FRAMES; + for (i = 0; i < frames; i++) + { + int source = consumed + i; + int32_t left; + int32_t right; + + if (audio_stereo_mode == STEREO_INTERLEAVED) + { + left = audio_sample_value(channel1, source * 2); + right = audio_sample_value(channel1, source * 2 + 1); + } + else + { + left = audio_sample_value(channel1, source); + right = audio_stereo_mode == STEREO_MONO ? left : + audio_sample_value(channel2, source); + } + audio_pcm[i * 2] = audio_clip16(left); + audio_pcm[i * 2 + 1] = audio_clip16(right); + } + while (written < frames && audio_action != CODEC_ACTION_HALT) + { + int amount; + + while (audio_paused && audio_action != CODEC_ACTION_HALT) + rb->sleep(1); + amount = video_pcm_write(audio_pcm + written * 2, + frames - written); + if (amount == 0) + { + rb->sleep(1); + continue; + } + written += amount; + } + consumed += frames; + if (!audio_is_ready && audio_sample_rate > 0 && + video_pcm_buffered_samples() >= + audio_sample_rate * AUDIO_START_BUFFER_MS / 1000) + audio_is_ready = true; + } +} + +static void audio_codec_api_init(void) +{ + rb->memset(&video_ci, 0, sizeof(video_ci)); + + video_ci.dsp = rb->dsp_get_config(CODEC_IDX_AUDIO); + video_ci.codec_get_buffer = audio_codec_get_buffer; + video_ci.pcmbuf_insert = audio_pcm_insert; + video_ci.set_elapsed = audio_set_elapsed; + video_ci.read_filebuf = audio_read_filebuf; + video_ci.request_buffer = audio_request_buffer; + video_ci.advance_buffer = audio_advance_buffer; + video_ci.seek_buffer = audio_seek_buffer; + video_ci.seek_complete = audio_seek_complete; + video_ci.set_offset = audio_set_offset; + video_ci.configure = audio_configure; + video_ci.get_command = audio_get_command; + video_ci.loop_track = audio_loop_track; + video_ci.strip_filesize = audio_strip_filesize; + +#if defined(ARM_NEED_DIV0) + video_ci.__div0 = rb->__div0; +#endif + video_ci.sleep = rb->sleep; + video_ci.yield = rb->yield; + +#if NUM_CORES > 1 + video_ci.create_thread = rb->create_thread; + video_ci.thread_thaw = rb->thread_thaw; + video_ci.thread_wait = rb->thread_wait; + video_ci.semaphore_init = rb->semaphore_init; + video_ci.semaphore_wait = rb->semaphore_wait; + video_ci.semaphore_release = rb->semaphore_release; +#endif + + video_ci.commit_dcache = rb->commit_dcache; + video_ci.commit_discard_dcache = rb->commit_discard_dcache; + video_ci.commit_discard_idcache = rb->commit_discard_idcache; + video_ci.strcpy = rb->strcpy; + video_ci.strlen = rb->strlen; + video_ci.strcmp = rb->strcmp; + video_ci.strcat = rb->strcat; + video_ci.memset = rb->memset; + video_ci.memcpy = rb->memcpy; + video_ci.memmove = rb->memmove; + video_ci.memcmp = rb->memcmp; + video_ci.memchr = rb->memchr; +#if defined(DEBUG) || defined(SIMULATOR) + video_ci.debugf = rb->debugf; +#endif +#ifdef ROCKBOX_HAS_LOGF + video_ci.logf = rb->logf; +#endif + video_ci.qsort = rb->qsort; + +#ifdef RB_PROFILE + video_ci.profile_thread = rb->profile_thread; + video_ci.profstop = rb->profstop; + video_ci.profile_func_enter = rb->profile_func_enter; + video_ci.profile_func_exit = rb->profile_func_exit; +#endif + +#ifdef HAVE_RECORDING + video_ci.round_value_to_list32 = rb->round_value_to_list32; +#endif + video_ci.panicf = rb->panicf; +} + +static void audio_codec_worker(void) +{ + int status; +#ifdef HAVE_PRIORITY_SCHEDULING + /* Match mpegplayer: core DSP/AAC yields often enough that the normal + * codec priority can be starved by the video render loop. */ + int old_priority = rb->thread_set_priority(rb->thread_self(), + PRIORITY_PLAYBACK - 4); +#endif + + status = rb->codec_load_file("aac", &video_ci); + if (status >= 0) + status = rb->codec_run_proc(); + rb->codec_close(); +#ifdef HAVE_PRIORITY_SCHEDULING + rb->thread_set_priority(rb->thread_self(), old_priority); +#endif + if (status < 0 && audio_action != CODEC_ACTION_HALT) + audio_has_error = true; + audio_is_ready = true; + audio_is_playing = false; +} + +int video_audio_init(const char *filepath, + const struct mp4v_demux_res *demux, + void *codec_workspace, size_t workspace_size, + void *pcm_buffer, size_t pcm_buffer_size) +{ + size_t required_workspace = video_audio_workspace_size(demux); + + if (audio_initialized || filepath == NULL || demux == NULL || + codec_workspace == NULL || pcm_buffer == NULL || + required_workspace == 0 || + workspace_size < required_workspace || + demux->audio_format != MAKEFOURCC('m', 'p', '4', 'a') || + demux->audio_sample_rate == 0 || demux->audio_num_samples == 0) + return -1; + + audio_fd = rb->open(filepath, O_RDONLY); + if (audio_fd < 0) + return -1; + audio_file_size = rb->ffilesize(audio_fd); + if (audio_file_size <= 0) + { + rb->close(audio_fd); + audio_fd = -1; + return -1; + } + + audio_codec_workspace = codec_workspace; + audio_codec_workspace_size = workspace_size; + audio_file_buffer_offset = 0; + audio_file_buffer_length = 0; + audio_sample_rate = demux->audio_sample_rate; + audio_sample_depth = 29; + audio_stereo_mode = STEREO_NONINTERLEAVED; + audio_action = CODEC_ACTION_NULL; + audio_seek_target_ms = 0; + audio_paused = false; + audio_is_ready = false; + audio_is_playing = false; + audio_has_error = false; + + rb->memset(&video_id3, 0, sizeof(video_id3)); + video_id3.codectype = AFMT_MP4_AAC; + video_id3.frequency = demux->audio_sample_rate; + video_id3.lead_trim = demux->audio_lead_trim; + + audio_codec_api_init(); + video_ci.filesize = audio_file_size; + video_ci.curpos = 0; + video_ci.id3 = &video_id3; + video_ci.audio_hid = -1; + if (!video_pcm_init(audio_sample_rate, pcm_buffer, pcm_buffer_size)) + { + rb->close(audio_fd); + audio_fd = -1; + audio_codec_workspace = NULL; + audio_codec_workspace_size = 0; + return -1; + } + audio_initialized = true; + return 0; +} + +void video_audio_play(void) +{ + if (!audio_initialized || audio_is_playing) + return; + audio_action = CODEC_ACTION_NULL; + audio_paused = false; + audio_is_playing = true; + rb->codec_thread_do_callback(audio_codec_worker, NULL); +} + +void video_audio_pause(void) +{ + audio_paused = true; +} + +void video_audio_resume(void) +{ + audio_paused = false; +} + +void video_audio_seek(uint32_t target_ms) +{ + if (!audio_initialized || !audio_is_playing) + return; + + audio_is_ready = false; + audio_seek_target_ms = target_ms; + video_pcm_flush(target_ms, audio_sample_rate); + audio_action = CODEC_ACTION_SEEK_TIME; +} + +void video_audio_stop(void) +{ + if (!audio_initialized) + return; + + audio_action = CODEC_ACTION_HALT; + audio_paused = false; + rb->codec_thread_do_callback(NULL, NULL); + video_pcm_stop(); + if (audio_fd >= 0) + { + rb->close(audio_fd); + audio_fd = -1; + } + audio_codec_workspace = NULL; + audio_codec_workspace_size = 0; + audio_initialized = false; + audio_is_playing = false; +} + +bool video_audio_ready(void) +{ + return audio_is_ready || audio_has_error; +} + +bool video_audio_is_active(void) +{ + return audio_is_playing; +} + +bool video_audio_failed(void) +{ + return audio_has_error; +} diff --git a/apps/plugins/h264_player/video_audio.h b/apps/plugins/h264_player/video_audio.h new file mode 100644 index 0000000000..9af846abb5 --- /dev/null +++ b/apps/plugins/h264_player/video_audio.h @@ -0,0 +1,74 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2025-2026 David Cormier + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#ifndef VIDEO_AUDIO_H +#define VIDEO_AUDIO_H + +/* + * H.264 player audio decode service. + * + * Decodes AAC audio via Rockbox's aac.codec and feeds a PCM ring buffer. + * Uses the audio clock from video_pcm for A/V synchronization. + * + */ + +#include +#include +#include +#include "mp4_demux.h" + +/* Return enough codec heap to retain every AAC chunk offset. Apple M4V files + * interleave audio and video densely, so a sparse chunk map cannot decode the + * audio stream correctly. Returns 0 if the size cannot be represented. */ +size_t video_audio_workspace_size(const struct mp4v_demux_res *demux); + +/* Initialize audio decoder and thread. + * filepath: MP4 file path (opens its own fd for independent seeks) + * demux: parsed MP4 with audio track data (audio_format != 0) + * Returns 0 on success, -1 on error. */ +int video_audio_init(const char *filepath, + const struct mp4v_demux_res *demux, + void *codec_workspace, size_t workspace_size, + void *pcm_buffer, size_t pcm_buffer_size); + +/* Start audio playback (begins decoding and filling PCM buffer). */ +void video_audio_play(void); + +/* Pause audio decode (PCM buffer drains, then silence). */ +void video_audio_pause(void); + +/* Resume audio decode after pause. */ +void video_audio_resume(void); + +/* Seek audio to target time in milliseconds. Flushes PCM buffer. */ +void video_audio_seek(uint32_t target_ms); + +/* Stop audio thread and release resources. Blocks until thread exits. */ +void video_audio_stop(void); + +/* Check if audio thread has pre-filled enough PCM data to start. */ +bool video_audio_ready(void); + +/* Check if audio thread is actively decoding (false after EOF or error). */ +bool video_audio_is_active(void); + +/* True if the dynamically loaded AAC codec failed to load or decode. */ +bool video_audio_failed(void); + +#endif /* VIDEO_AUDIO_H */ diff --git a/apps/plugins/h264_player/video_pcm.c b/apps/plugins/h264_player/video_pcm.c new file mode 100644 index 0000000000..82305f2e53 --- /dev/null +++ b/apps/plugins/h264_player/video_pcm.c @@ -0,0 +1,259 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2025-2026 David Cormier + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +/* + * PCM ring buffer for video player audio output. + * + * Simplified version of mpegplayer's pcm_output.c. + * Single-producer (audio decode thread), single-consumer (DMA ISR). + * Audio-master clock via monotonic sample counter. + * + * get_more() runs in DMA ISR context: no yield, sleep, mutex, or file I/O. + */ + +#include "plugin.h" +#include "system.h" +#include "kernel.h" +#include "pcm.h" +#include "pcm_mixer.h" +#include "pcm_sampr.h" +#include "video_pcm.h" + +#include + +/* Silence clip for underrun (256 stereo samples = ~5.8ms at 44.1kHz) */ +#define SILENCE_SAMPLES 256 + +static int16_t *pcm_buffer; +static uint32_t pcm_buffer_frames; +static int16_t silence[SILENCE_SAMPLES * 2]; + +/* Read/write positions in stereo samples (wrap via modulo) */ +static volatile uint32_t pcm_read_pos; +static volatile uint32_t pcm_write_pos; + +/* Master clock: stereo samples consumed by DMA (uint32_t sufficient + * for >27 hours at 44.1kHz; avoids torn reads on 32-bit ARM) */ +static volatile uint32_t clock_samples; + +/* Clock base: absolute time (ms) at last flush/init */ +static uint32_t clock_base_ms; +static uint32_t clock_sample_rate; + +/* Flush guard: prevents write during flush (set by main, checked by audio) */ +static volatile bool flush_pending; +static struct mutex write_mutex; + +/* Saved mixer frequency for restore on stop */ +static unsigned int saved_sampr; + +static uint32_t pcm_used(void) +{ + return pcm_write_pos - pcm_read_pos; +} + +static uint32_t pcm_free(void) +{ + uint32_t used = pcm_used(); + + return used < pcm_buffer_frames ? pcm_buffer_frames - used : 0; +} + +/* DMA callback runs in ISR context. + * Pulls from ring buffer, advances clock. On underrun, plays silence. */ +static void video_pcm_get_more(const void **start, size_t *size) +{ + uint32_t avail = pcm_used(); + + if (avail > 0) + { + uint32_t rd = pcm_read_pos % pcm_buffer_frames; + uint32_t chunk = avail; + + /* Don't wrap around the buffer end */ + if (rd + chunk > pcm_buffer_frames) + chunk = pcm_buffer_frames - rd; + + /* Limit chunk to one mixer frame so the entire returned buffer + * is consumed before pcm_read_pos advances. Without this, the + * mixer stores our pointer and gradually reads over 8 callbacks + * while the write side sees the positions as "free" and + * overwrites them, causing audible fragment repetition. */ + if (chunk > MIX_FRAME_SAMPLES) + chunk = MIX_FRAME_SAMPLES; + + *start = &pcm_buffer[rd * 2]; + *size = chunk * 4; /* stereo 16-bit = 4 bytes per sample */ + pcm_read_pos += chunk; + clock_samples += chunk; + } + else + { + /* Underrun: play silence. Do NOT advance the clock; + * advancing would create a permanent A/V offset because the + * audio content hasn't actually been played yet. */ + *start = silence; + *size = sizeof(silence); + } +} + +static const struct mixer_play_cbs video_pcm_cbs = { + .get_more = video_pcm_get_more, +}; + +bool video_pcm_init(uint32_t sample_rate, void *buffer, size_t buffer_size) +{ + if (sample_rate == 0 || buffer == NULL || + buffer_size / (2 * sizeof(int16_t)) < MIX_FRAME_SAMPLES) + return false; + + /* Save current frequency for restore */ + saved_sampr = rb->mixer_get_frequency(); + pcm_buffer = buffer; + pcm_buffer_frames = buffer_size / (2 * sizeof(int16_t)); + + /* Reset buffer state */ + pcm_read_pos = 0; + pcm_write_pos = 0; + clock_samples = 0; + clock_base_ms = 0; + clock_sample_rate = sample_rate; + flush_pending = false; + rb->mutex_init(&write_mutex); + rb->memset(silence, 0, sizeof(silence)); + + /* Set sample rate BEFORE starting playback + * (mixer_set_frequency calls mixer_reset which stops all channels) */ +#if INPUT_SRC_CAPS != 0 + rb->audio_set_input_source(AUDIO_SRC_PLAYBACK, SRCF_PLAYBACK); + rb->audio_set_output_source(AUDIO_SRC_PLAYBACK); +#endif + rb->mixer_set_frequency(sample_rate); + + /* Start playback with DMA callback */ + rb->mixer_channel_set_amplitude(PCM_MIXER_CHAN_PLAYBACK, MIX_AMP_UNITY); + rb->mixer_channel_play_data(PCM_MIXER_CHAN_PLAYBACK, + &video_pcm_cbs, NULL, 0); + return true; +} + +int video_pcm_write(const int16_t *pcm, int stereo_samples) +{ + int written = 0; + + rb->mutex_lock(&write_mutex); + if (flush_pending) + { + rb->mutex_unlock(&write_mutex); + return 0; + } + + while (written < stereo_samples) + { + uint32_t space = pcm_free(); + if (space == 0 || flush_pending) + break; + + uint32_t wr = pcm_write_pos % pcm_buffer_frames; + uint32_t chunk = stereo_samples - written; + + if (chunk > space) + chunk = space; + + /* Don't wrap around the buffer end */ + if (wr + chunk > pcm_buffer_frames) + chunk = pcm_buffer_frames - wr; + + rb->memcpy(&pcm_buffer[wr * 2], &pcm[written * 2], chunk * 4); + pcm_write_pos += chunk; + written += chunk; + } + + rb->mutex_unlock(&write_mutex); + return written; +} + +uint32_t video_pcm_get_clock_ms(void) +{ + uint32_t samples = clock_samples; /* atomic 32-bit read on ARM */ + if (clock_sample_rate == 0) + return clock_base_ms; + return clock_base_ms + (uint32_t)((uint64_t)samples * 1000 + / clock_sample_rate); +} + +void video_pcm_flush(uint32_t base_ms, uint32_t sample_rate) +{ + /* Serialize against the codec producer before resetting its indexes. */ + rb->mutex_lock(&write_mutex); + flush_pending = true; + + /* Stop mixer channel to invalidate any stale buffer pointer, + * then reset state, then restart (mpegplayer pattern). */ + rb->mixer_channel_stop(PCM_MIXER_CHAN_PLAYBACK); + + pcm_read_pos = 0; + pcm_write_pos = 0; + clock_samples = 0; + clock_base_ms = base_ms; + clock_sample_rate = sample_rate; + + /* Restart mixer with fresh callback */ + rb->mixer_channel_set_amplitude(PCM_MIXER_CHAN_PLAYBACK, MIX_AMP_UNITY); + rb->mixer_channel_play_data(PCM_MIXER_CHAN_PLAYBACK, + &video_pcm_cbs, NULL, 0); + + flush_pending = false; + rb->mutex_unlock(&write_mutex); +} + +void video_pcm_pause(bool pause) +{ + rb->mixer_channel_play_pause(PCM_MIXER_CHAN_PLAYBACK, !pause); +} + +void video_pcm_stop(void) +{ + flush_pending = false; + + /* Serialize callback removal before plugin-owned memory is released. */ + rb->mixer_channel_stop(PCM_MIXER_CHAN_PLAYBACK); + + /* Restore original sample rate */ + if (saved_sampr != 0) + rb->mixer_set_frequency(saved_sampr); + pcm_buffer = NULL; + pcm_buffer_frames = 0; +} + +bool video_pcm_has_space(int stereo_samples) +{ + return pcm_free() >= (uint32_t)stereo_samples; +} + +bool video_pcm_empty(void) +{ + return pcm_used() == 0; +} + +uint32_t video_pcm_buffered_samples(void) +{ + return pcm_used(); +} diff --git a/apps/plugins/h264_player/video_pcm.h b/apps/plugins/h264_player/video_pcm.h new file mode 100644 index 0000000000..671341ae34 --- /dev/null +++ b/apps/plugins/h264_player/video_pcm.h @@ -0,0 +1,65 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2025-2026 David Cormier + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#ifndef VIDEO_PCM_H +#define VIDEO_PCM_H + +/* + * PCM ring buffer for H.264 player audio output. + * + * Simplified version of mpegplayer's pcm_output.c. + * Audio-master clock: DMA callback advances clock_samples monotonically. + */ + +#include +#include +#include + +/* Initialize PCM output at the given sample rate. + * Saves and changes mixer frequency. */ +bool video_pcm_init(uint32_t sample_rate, void *buffer, size_t buffer_size); + +/* Write interleaved stereo int16 PCM to the ring buffer. + * Returns number of stereo samples actually written (may be less if full). */ +int video_pcm_write(const int16_t *pcm, int stereo_samples); + +/* Get audio master clock in milliseconds. + * Returns base_ms + (samples_played * 1000 / sample_rate). */ +uint32_t video_pcm_get_clock_ms(void); + +/* Flush ring buffer and set clock base (for seek). + * After flush, get_clock returns base_ms + samples_played. */ +void video_pcm_flush(uint32_t base_ms, uint32_t sample_rate); + +/* Pause/resume PCM output. */ +void video_pcm_pause(bool pause); + +/* Stop PCM output and restore original mixer frequency. */ +void video_pcm_stop(void); + +/* Check if ring buffer has space for at least n stereo samples. */ +bool video_pcm_has_space(int stereo_samples); + +/* Check if ring buffer is empty (all data played). */ +bool video_pcm_empty(void); + +/* Return number of stereo samples currently buffered. */ +uint32_t video_pcm_buffered_samples(void); + +#endif /* VIDEO_PCM_H */ diff --git a/apps/plugins/h264_player/video_playback.c b/apps/plugins/h264_player/video_playback.c new file mode 100644 index 0000000000..b73cb7694f --- /dev/null +++ b/apps/plugins/h264_player/video_playback.c @@ -0,0 +1,1142 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2025-2026 David Cormier + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +/* + * S5L8702 hardware H.264 player. + * + * This is deliberately a narrow production contract: non-fragmented MP4/M4V, + * the measured iTunes 9.2.1 Constrained Baseline syntax (Level 1.3 or 3.0, + * one or two advertised reference pictures and ordered slices), + * <= 640x480, <= 30 fps, with optional AAC-LC audio. + * + * Copyright (C) 2025-2026 David Cormier + */ + +#include "plugin.h" +#include "backlight.h" +#include "button.h" +#include "crc32.h" +#include "dir.h" +#include "file.h" +#include "font.h" +#include "kernel.h" +#include "lang.h" +#include "lcd.h" +#include "menu.h" +#include "misc.h" +#include "mp4_demux.h" +#include "pcmbuf.h" +#include "rbpaths.h" +#include "settings.h" +#include "sound.h" +#include "splash.h" +#include "system.h" +#include "video_audio.h" +#include "video_pcm.h" +#include "video_playback.h" + +#include +#include +#include + +#define VIDEO_MAX_WIDTH 640 +#define VIDEO_MAX_HEIGHT 480 +#define VIDEO_MAX_LEVEL 30 +#define VIDEO_MAX_FPS 30 +#define VIDEO_MAX_SAMPLES 400000u +#define VIDEO_MAX_AUDIO_SAMPLES 600000u +#define VIDEO_READ_BUFFER (512u * 1024u) +#define VIDEO_OUTPUT_BUFFER (LCD_WIDTH * LCD_HEIGHT * 3u / 2u) +#define VIDEO_PCM_BUFFER (256u * 1024u) +#define VIDEO_OSD_SHOW_TICKS (HZ * 4) +#define VIDEO_RESUME_MAGIC 0x48323634u +#define VIDEO_RESUME_FILE PLUGIN_APPS_DATA_DIR \ + "/h264-resume-%08lx.dat" + +enum video_input_action +{ + VIDEO_INPUT_NONE = 0, + VIDEO_INPUT_EXIT, + VIDEO_INPUT_USB, + VIDEO_INPUT_MENU, + VIDEO_INPUT_SEEK_BACK, + VIDEO_INPUT_SEEK_FORWARD, +}; + +struct video_resume_record +{ + uint32_t magic; + uint32_t path_crc; + int32_t frame; + int32_t total_frames; +}; + +struct video_pool +{ + uint8_t *cursor; + uint8_t *end; +}; + +struct video_timing +{ + uint32_t run; + uint32_t in_run; + uint64_t ticks; +}; + +static void *video_pool_take(struct video_pool *pool, size_t size, + size_t alignment) +{ + uintptr_t current; + uintptr_t aligned; + + if (pool == NULL || alignment == 0) + return NULL; + current = (uintptr_t)pool->cursor; + aligned = (current + alignment - 1) & ~(uintptr_t)(alignment - 1); + if (aligned < current || aligned > (uintptr_t)pool->end || + size > (size_t)(pool->end - (uint8_t *)aligned)) + return NULL; + pool->cursor = (uint8_t *)aligned + size; + return (void *)aligned; +} + +static bool video_tables_sane(const struct mp4v_demux_res *demux) +{ + uint64_t timed_samples = 0; + uint32_t i; + bool video_sane = + demux->num_samples > 0 && + demux->num_samples <= VIDEO_MAX_SAMPLES && + demux->num_stco > 0 && + demux->num_stco <= demux->num_samples && + demux->num_stsc > 0 && + demux->num_stsc <= demux->num_stco && + demux->num_stts > 0 && + demux->num_stss <= demux->num_samples && + demux->timescale > 0; + bool audio_sane = + (demux->audio_num_samples == 0 && + demux->audio_num_stco == 0 && demux->audio_num_stsc == 0) || + (demux->audio_num_samples > 0 && + demux->audio_num_samples <= VIDEO_MAX_AUDIO_SAMPLES && + demux->audio_num_stco > 0 && + demux->audio_num_stco <= demux->audio_num_samples && + demux->audio_num_stsc > 0 && + demux->audio_num_stsc <= demux->audio_num_stco); + + if (!video_sane || !audio_sane) + return false; + for (i = 0; i < demux->num_stts; i++) + { + if (demux->stts[i].sample_count == 0 || + demux->stts[i].sample_delta == 0 || + (uint64_t)demux->stts[i].sample_delta * VIDEO_MAX_FPS < + demux->timescale) + return false; + timed_samples += demux->stts[i].sample_count; + if (timed_samples > demux->num_samples) + return false; + } + return timed_samples == demux->num_samples; +} + +/* Validate the actual video mapping after the second, full table pass. The + * old independent stco/stsc caps rejected long files authored by iTunes, + * whose normal layout is one video sample per chunk and may therefore have + * hundreds of thousands of chunks. The MP4 relationships themselves give + * tighter bounds: every chunk contains samples and every stsc run starts at + * a real chunk. */ +static bool video_stsc_sane(const struct mp4v_demux_res *demux) +{ + uint64_t mapped_samples = 0; + uint32_t i; + + if (demux->stsc == NULL || demux->num_stsc == 0 || + demux->stsc_cap < demux->num_stsc || + demux->sample_sizes == NULL || + demux->sample_sizes_cap < demux->num_samples || + demux->chunk_offsets == NULL || + demux->chunk_offsets_cap < demux->num_stco || + (demux->num_stss > 0 && + (demux->sync_samples == NULL || + demux->sync_samples_cap < demux->num_stss))) + return false; + for (i = 0; i < demux->num_stsc; i++) + { + const struct mp4v_stsc_entry *entry = &demux->stsc[i]; + uint32_t next_chunk = i + 1 < demux->num_stsc ? + demux->stsc[i + 1].first_chunk : demux->num_stco + 1; + + if ((i == 0 && entry->first_chunk != 1) || + entry->first_chunk == 0 || entry->first_chunk > demux->num_stco || + next_chunk <= entry->first_chunk || + next_chunk > demux->num_stco + 1 || + entry->samples_per_chunk == 0 || entry->sample_desc_index == 0) + return false; + mapped_samples += + (uint64_t)(next_chunk - entry->first_chunk) * + entry->samples_per_chunk; + if (mapped_samples > demux->num_samples) + return false; + } + if (mapped_samples != demux->num_samples) + return false; + for (i = 0; i < demux->num_stss; i++) + { + if (demux->sync_samples[i] == 0 || + demux->sync_samples[i] > demux->num_samples || + (i > 0 && demux->sync_samples[i] <= + demux->sync_samples[i - 1])) + return false; + } + return true; +} + +static uint32_t video_pts_ms(const struct mp4v_demux_res *demux, + const struct video_timing *timing) +{ + if (demux->timescale == 0) + return 0; + return (uint32_t)(timing->ticks * 1000u / demux->timescale); +} + +static void video_timing_advance(const struct mp4v_demux_res *demux, + struct video_timing *timing) +{ + const struct mp4v_stts_entry *entry; + + if (timing->run >= demux->num_stts) + return; + entry = &demux->stts[timing->run]; + timing->ticks += entry->sample_delta; + timing->in_run++; + if (timing->in_run >= entry->sample_count) + { + timing->run++; + timing->in_run = 0; + } +} + +static uint32_t video_duration_ms(const struct mp4v_demux_res *demux) +{ + uint64_t ticks = 0; + uint32_t i; + + if (demux->timescale == 0) + return 0; + for (i = 0; i < demux->num_stts; i++) + ticks += (uint64_t)demux->stts[i].sample_count * + demux->stts[i].sample_delta; + return (uint32_t)(ticks * 1000u / demux->timescale); +} + +static void video_timing_for_sample(const struct mp4v_demux_res *demux, + uint32_t sample, + struct video_timing *timing) +{ + uint32_t remaining = sample; + + rb->memset(timing, 0, sizeof(*timing)); + while (timing->run < demux->num_stts) + { + const struct mp4v_stts_entry *entry = + &demux->stts[timing->run]; + + if (remaining < entry->sample_count) + { + timing->in_run = remaining; + timing->ticks += (uint64_t)remaining * entry->sample_delta; + return; + } + timing->ticks += (uint64_t)entry->sample_count * + entry->sample_delta; + remaining -= entry->sample_count; + timing->run++; + } +} + +static uint32_t video_sample_for_ms(const struct mp4v_demux_res *demux, + uint32_t target_ms, + struct video_timing *timing) +{ + uint64_t target_ticks; + uint64_t ticks = 0; + uint32_t sample = 0; + uint32_t run; + + if (demux->timescale == 0) + { + rb->memset(timing, 0, sizeof(*timing)); + return 0; + } + target_ticks = (uint64_t)target_ms * demux->timescale / 1000u; + for (run = 0; run < demux->num_stts; run++) + { + const struct mp4v_stts_entry *entry = &demux->stts[run]; + uint64_t run_ticks = (uint64_t)entry->sample_count * + entry->sample_delta; + + if (target_ticks < ticks + run_ticks) + { + uint32_t in_run = entry->sample_delta == 0 ? 0 : + (uint32_t)((target_ticks - ticks) / entry->sample_delta); + + if (in_run >= entry->sample_count) + in_run = entry->sample_count - 1; + sample += in_run; + break; + } + ticks += run_ticks; + sample += entry->sample_count; + } + if (sample >= demux->num_samples) + sample = demux->num_samples - 1; + while (sample > 0 && !mp4v_is_keyframe(demux, sample)) + sample--; + video_timing_for_sample(demux, sample, timing); + return sample; +} + +static void video_resume_filename(const char *path, char *filename, + size_t size, uint32_t *crc_out) +{ + uint32_t crc = rb->crc_32(path, rb->strlen(path), 0xffffffff); + + rb->snprintf(filename, size, VIDEO_RESUME_FILE, (unsigned long)crc); + if (crc_out != NULL) + *crc_out = crc; +} + +static uint32_t video_resume_load(const char *path, uint32_t total_samples) +{ + struct video_resume_record record; + char filename[MAX_PATH]; + uint32_t crc; + int fd; + + video_resume_filename(path, filename, sizeof(filename), &crc); + fd = rb->open(filename, O_RDONLY); + if (fd < 0) + return 0; + if (rb->read(fd, &record, sizeof(record)) != sizeof(record)) + { + rb->close(fd); + return 0; + } + rb->close(fd); + if (record.magic != VIDEO_RESUME_MAGIC || record.path_crc != crc || + record.total_frames != (int32_t)total_samples || record.frame <= 0 || + record.frame >= (int32_t)(total_samples * 95u / 100u)) + return 0; + return (uint32_t)record.frame; +} + +static void video_resume_save(const char *path, uint32_t sample, + uint32_t total_samples) +{ + struct video_resume_record record; + char filename[MAX_PATH]; + uint32_t crc; + int fd; + + video_resume_filename(path, filename, sizeof(filename), &crc); + if (sample == 0 || sample >= total_samples * 95u / 100u) + { + rb->remove(filename); + return; + } + rb->mkdir(PLUGIN_APPS_DATA_DIR); + record.magic = VIDEO_RESUME_MAGIC; + record.path_crc = crc; + record.frame = (int32_t)sample; + record.total_frames = (int32_t)total_samples; + fd = rb->open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (fd < 0) + return; + (void)rb->write(fd, &record, sizeof(record)); + rb->close(fd); +} + +static void video_resume_clear(const char *path) +{ + char filename[MAX_PATH]; + + video_resume_filename(path, filename, sizeof(filename), NULL); + rb->remove(filename); +} + +static void video_volume_change(int delta) +{ + int minimum = rb->sound_min(SOUND_VOLUME); + int maximum = rb->sound_max(SOUND_VOLUME); + int volume = rb->global_status->volume + delta; + + if (volume < minimum) + volume = minimum; + if (volume > maximum) + volume = maximum; + if (volume != rb->global_status->volume) + rb->sound_set(SOUND_VOLUME, volume); +} + +static void video_set_paused(bool pause, bool *paused, long *start_tick, + long *pause_started, bool have_audio, + bool *cpu_boosted) +{ + if (pause == *paused) + return; + if (pause) + { + *paused = true; + *pause_started = (*rb->current_tick); + if (have_audio) + { + video_audio_pause(); + video_pcm_pause(true); + } + if (*cpu_boosted) + { + rb->cpu_boost(false); + *cpu_boosted = false; + } + } + else + { + if (!*cpu_boosted) + { + rb->cpu_boost(true); + *cpu_boosted = true; + } + *paused = false; + *start_tick += (*rb->current_tick) - *pause_started; + if (have_audio) + { + video_audio_resume(); + video_pcm_pause(false); + } + } +} + +static enum video_input_action video_input(bool *paused, long *start_tick, + long *pause_started, + bool have_audio, + bool *cpu_boosted, + long *overlay_until, + bool *redraw) +{ + int action = rb->get_action(CONTEXT_WPS, TIMEOUT_NOBLOCK); + + *redraw = false; + if (action == ACTION_NONE) + return VIDEO_INPUT_NONE; + if (action == ACTION_WPS_MENU) + return VIDEO_INPUT_MENU; + if (action == ACTION_WPS_STOP) + return VIDEO_INPUT_EXIT; + if (action == ACTION_WPS_VOLUP) + { + video_volume_change(1); + *overlay_until = (*rb->current_tick) + VIDEO_OSD_SHOW_TICKS; + *redraw = true; + return VIDEO_INPUT_NONE; + } + if (action == ACTION_WPS_VOLDOWN) + { + video_volume_change(-1); + *overlay_until = (*rb->current_tick) + VIDEO_OSD_SHOW_TICKS; + *redraw = true; + return VIDEO_INPUT_NONE; + } + if (action == ACTION_WPS_SKIPPREV || action == ACTION_WPS_SEEKBACK) + return VIDEO_INPUT_SEEK_BACK; + if (action == ACTION_WPS_SKIPNEXT || action == ACTION_WPS_SEEKFWD) + return VIDEO_INPUT_SEEK_FORWARD; + if (action == ACTION_WPS_PLAY) + { + video_set_paused(!*paused, paused, start_tick, pause_started, + have_audio, cpu_boosted); + *overlay_until = (*rb->current_tick) + VIDEO_OSD_SHOW_TICKS; + *redraw = true; + } + if (rb->default_event_handler(action) == SYS_USB_CONNECTED) + return VIDEO_INPUT_USB; + return VIDEO_INPUT_NONE; +} + +enum video_menu_item +{ + VIDEO_MENU_RESUME = 0, + VIDEO_MENU_QUIT, +}; + +static enum video_input_action video_show_menu( + bool *paused, long *start_tick, long *pause_started, bool have_audio, + bool *cpu_boosted, long *overlay_until, bool *redraw) +{ + bool resume_after = !*paused; + int selection; + + MENUITEM_STRINGLIST(menu, "H.264 Player", NULL, + ID2P(LANG_RESUME_PLAYBACK), + ID2P(LANG_MENU_QUIT)); + + video_set_paused(true, paused, start_tick, pause_started, have_audio, + cpu_boosted); + rb->button_clear_queue(); + selection = rb->do_menu(&menu, NULL, NULL, false); + rb->button_clear_queue(); + if (selection == MENU_ATTACHED_USB) + return VIDEO_INPUT_USB; + if (selection == VIDEO_MENU_QUIT) + return VIDEO_INPUT_EXIT; + if (resume_after) + video_set_paused(false, paused, start_tick, pause_started, + have_audio, cpu_boosted); + *overlay_until = (*rb->current_tick) + VIDEO_OSD_SHOW_TICKS; + *redraw = true; + return VIDEO_INPUT_NONE; +} + +static void video_scale_plane(uint8_t *destination, int destination_stride, + int destination_width, int destination_height, + const uint8_t *source, int source_stride, + int source_width, int source_height) +{ + uint32_t source_y = 0; + uint32_t step_y; + uint32_t step_x; + int row; + + if (destination_width <= 0 || destination_height <= 0 || + source_width <= 0 || source_height <= 0) + return; + step_x = ((uint32_t)source_width << 16) / destination_width; + step_y = ((uint32_t)source_height << 16) / destination_height; + for (row = 0; row < destination_height; row++) + { + const uint8_t *source_row = source + + (source_y >> 16) * source_stride; + uint8_t *destination_row = destination + + row * destination_stride; + uint32_t source_x = 0; + int column; + + for (column = 0; column < destination_width; column++) + { + destination_row[column] = source_row[source_x >> 16]; + source_x += step_x; + } + source_y += step_y; + } +} + +static uint8_t video_clamp_yuv(int value) +{ + if (value < 0) + return 0; + if (value > 255) + return 255; + return (uint8_t)value; +} + +static void video_yuv_color(int red, int green, int blue, + uint8_t *y, uint8_t *u, uint8_t *v) +{ + *y = video_clamp_yuv( + ((66 * red + 129 * green + 25 * blue + 128) >> 8) + 16); + *u = video_clamp_yuv( + ((-38 * red - 74 * green + 112 * blue + 128) >> 8) + 128); + *v = video_clamp_yuv( + ((112 * red - 94 * green - 18 * blue + 128) >> 8) + 128); +} + +static void video_yuv_pixel(uint8_t * const *planes, int x, int y, + int red, int green, int blue) +{ + uint8_t py; + uint8_t pu; + uint8_t pv; + + if (x < 0 || x >= LCD_WIDTH || y < 0 || y >= LCD_HEIGHT) + return; + video_yuv_color(red, green, blue, &py, &pu, &pv); + planes[0][y * LCD_WIDTH + x] = py; + planes[1][(y / 2) * (LCD_WIDTH / 2) + x / 2] = pu; + planes[2][(y / 2) * (LCD_WIDTH / 2) + x / 2] = pv; +} + +static void video_yuv_rect(uint8_t * const *planes, int x, int y, + int width, int height, + int red, int green, int blue) +{ + int row; + int column; + + for (row = MAX(0, y); row < MIN(LCD_HEIGHT, y + height); row++) + for (column = MAX(0, x); + column < MIN(LCD_WIDTH, x + width); column++) + video_yuv_pixel(planes, column, row, red, green, blue); +} + +static int video_yuv_text_width(const char *text) +{ + struct font *font = rb->font_get(FONT_SYSFIXED); + int width = 0; + + if (font == NULL) + return 0; + while (*text != '\0') + width += rb->font_get_width(font, (unsigned char)*text++); + return width; +} + +static void video_yuv_text(uint8_t * const *planes, int x, int y, + const char *text, + int red, int green, int blue) +{ + struct font *font = rb->font_get(FONT_SYSFIXED); + + if (font == NULL || font->depth != 0) + return; + while (*text != '\0' && x < LCD_WIDTH) + { + unsigned char ch = *text++; + int glyph_width = rb->font_get_width(font, ch); + const unsigned char *bits = rb->font_get_bits(font, ch); + int column; + + for (column = 0; column < glyph_width; column++) + { + const unsigned char *source = bits + column; + int row; + + for (row = 0; row < (int)font->height; row++) + if (source[(row >> 3) * glyph_width] & + (1u << (row & 7))) + video_yuv_pixel(planes, x + column, y + row, + red, green, blue); + } + x += glyph_width; + } +} + +static void video_time_text(uint32_t milliseconds, char *text, size_t size) +{ + uint32_t seconds = milliseconds / 1000u; + + if (seconds >= 3600u) + rb->snprintf(text, size, "%lu:%02lu:%02lu", + (unsigned long)(seconds / 3600u), + (unsigned long)((seconds / 60u) % 60u), + (unsigned long)(seconds % 60u)); + else + rb->snprintf(text, size, "%lu:%02lu", + (unsigned long)(seconds / 60u), + (unsigned long)(seconds % 60u)); +} + +static void video_draw_status_icon(uint8_t * const *planes, int x, int y, + bool paused) +{ + int row; + + if (paused) + { + video_yuv_rect(planes, x, y, 2, 9, 255, 255, 255); + video_yuv_rect(planes, x + 5, y, 2, 9, 255, 255, 255); + return; + } + for (row = 0; row < 9; row++) + { + int width = row <= 4 ? row + 1 : 9 - row; + + video_yuv_rect(planes, x, y + row, width, 1, 255, 255, 255); + } +} + +static void video_draw_overlay(uint8_t * const *planes, bool paused, + uint32_t position_ms, uint32_t duration_ms, + bool visible) +{ + char current[20]; + char duration[20]; + char volume[20]; + char minimum_volume[20]; + const char *unit = rb->sound_unit(SOUND_VOLUME); + int top = LCD_HEIGHT - 34; + int bar_x = 2; + int bar_y = top + 21; + int bar_height = 8; + int volume_right = LCD_WIDTH - 2; + int volume_field_width; + int volume_width; + int space_width; + int bar_width; + int fill_width; + int duration_width; + + if (!visible) + return; + + video_time_text(position_ms, current, sizeof(current)); + video_time_text(duration_ms, duration, sizeof(duration)); + rb->snprintf(volume, sizeof(volume), "%d%s", + rb->sound_val2phys(SOUND_VOLUME, rb->global_status->volume), unit); + rb->snprintf(minimum_volume, sizeof(minimum_volume), "%d%s", + rb->sound_val2phys(SOUND_VOLUME, + rb->sound_min(SOUND_VOLUME)), unit); + volume_field_width = video_yuv_text_width(minimum_volume); + volume_width = video_yuv_text_width(volume); + space_width = video_yuv_text_width(" "); + bar_width = volume_right - volume_field_width - space_width - bar_x; + fill_width = duration_ms == 0 ? 0 : + (int)((uint64_t)MIN(position_ms, duration_ms) * + (bar_width - 2) / duration_ms); + duration_width = video_yuv_text_width(duration); + + /* Match MPEG player's raised purple OSD and element placement. */ + video_yuv_rect(planes, 0, top, LCD_WIDTH, 34, 115, 117, 189); + video_yuv_rect(planes, 0, top, LCD_WIDTH, 1, 220, 221, 239); + video_yuv_rect(planes, 0, top + 1, LCD_WIDTH, 1, 159, 160, 210); + video_yuv_rect(planes, 0, LCD_HEIGHT - 2, LCD_WIDTH, 1, 93, 95, 153); + video_yuv_rect(planes, 0, LCD_HEIGHT - 1, LCD_WIDTH, 1, 57, 58, 94); + video_yuv_text(planes, bar_x, top + 6, current, 255, 255, 255); + video_yuv_text(planes, bar_x + bar_width - duration_width, top + 6, + duration, 255, 255, 255); + video_draw_status_icon(planes, + bar_x + (bar_width - 7) / 2, + top + 5, paused); + video_yuv_rect(planes, bar_x, bar_y, bar_width, bar_height, + 255, 255, 255); + video_yuv_rect(planes, bar_x + 1, bar_y + 1, + bar_width - 2, bar_height - 2, 0, 0, 0); + video_yuv_rect(planes, bar_x + 1, bar_y + 1, + fill_width, bar_height - 2, 255, 255, 255); + video_yuv_text(planes, volume_right - volume_width, bar_y, + volume, 255, 255, 255); +} + +static void video_draw_frame(const uint8_t *y, const uint8_t *cb, + const uint8_t *cr, int width, int height, + int stride, uint8_t *scaled, bool paused, + uint32_t position_ms, uint32_t duration_ms, + bool overlay_visible) +{ + unsigned char *planes[3]; + int draw_width; + int draw_height; + int x; + int y_pos; + + if (y == NULL || cb == NULL || cr == NULL || scaled == NULL || + width <= 0 || height <= 0 || stride < width) + return; + + draw_width = LCD_WIDTH; + draw_height = (int)((int64_t)height * LCD_WIDTH / width); + if (draw_height > LCD_HEIGHT) + { + draw_height = LCD_HEIGHT; + draw_width = (int)((int64_t)width * LCD_HEIGHT / height); + } + draw_width &= ~1; + draw_height &= ~1; + x = ((LCD_WIDTH - draw_width) / 2) & ~1; + y_pos = ((LCD_HEIGHT - draw_height) / 2) & ~1; + + planes[0] = scaled; + planes[1] = scaled + LCD_WIDTH * LCD_HEIGHT; + planes[2] = planes[1] + LCD_WIDTH * LCD_HEIGHT / 4; + rb->memset(planes[0], 16, LCD_WIDTH * LCD_HEIGHT); + rb->memset(planes[1], 128, LCD_WIDTH * LCD_HEIGHT / 4); + rb->memset(planes[2], 128, LCD_WIDTH * LCD_HEIGHT / 4); + video_scale_plane(planes[0] + y_pos * LCD_WIDTH + x, LCD_WIDTH, + draw_width, draw_height, y, stride, width, height); + video_scale_plane(planes[1] + (y_pos / 2) * (LCD_WIDTH / 2) + x / 2, + LCD_WIDTH / 2, draw_width / 2, draw_height / 2, + cb, stride / 2, width / 2, height / 2); + video_scale_plane(planes[2] + (y_pos / 2) * (LCD_WIDTH / 2) + x / 2, + LCD_WIDTH / 2, draw_width / 2, draw_height / 2, + cr, stride / 2, width / 2, height / 2); + video_draw_overlay(planes, paused, position_ms, duration_ms, + overlay_visible); + rb->lcd_blit_yuv(planes, 0, 0, LCD_WIDTH, 0, 0, + LCD_WIDTH, LCD_HEIGHT); +} + +static void video_redraw_frame(struct vpu_h264 *decoder, uint8_t *scaled, + bool paused, uint32_t position_ms, + uint32_t duration_ms, bool overlay_visible) +{ + const uint8_t *frame_y; + const uint8_t *frame_cb; + const uint8_t *frame_cr; + int width; + int height; + int stride; + + rb->hw_h264->get_frame(decoder, &frame_y, &frame_cb, &frame_cr, + &width, &height, &stride); + video_draw_frame(frame_y, frame_cb, frame_cr, width, height, stride, + scaled, paused, position_ms, duration_ms, + overlay_visible); +} + +int video_h264_play(const char *filepath, void *buffer, size_t buffer_size) +{ + static struct mp4v_demux_res demux; + static uint32_t probe_video_sample[1]; + static uint32_t probe_video_chunk[1]; + static uint32_t probe_video_sync[1]; + struct video_pool pool; + struct video_timing timing; + struct vpu_h264 *decoder = NULL; + struct mp4v_stsc_entry *video_stsc; + uint32_t *video_samples; + uint32_t *video_chunks; + uint32_t *video_sync; + uint8_t *decoder_buffer; + uint8_t *read_buffer; + uint8_t *scale_buffer; + void *audio_codec_workspace; + void *pcm_buffer; + size_t decoder_size; + size_t audio_codec_workspace_size; + int video_fd = -1; + uint32_t duration_ms; + uint32_t displayed_ms = 0; + uint32_t sample = 0; + uint32_t last_sample = 0; + long start_tick; + long pause_started = 0; + long overlay_until; + bool paused = false; + bool cpu_boosted = false; + bool have_audio = false; + bool have_frame = false; + bool overlay_drawn = false; + bool audio_master = false; + enum video_input_action action = VIDEO_INPUT_NONE; + int result = -1; + + if (filepath == NULL || filepath[0] == '\0' || buffer == NULL || + buffer_size == 0) + return -1; + + rb->splash(0, "Loading video..."); + rb->cpu_boost(true); + cpu_boosted = true; + rb->memset(&demux, 0, sizeof(demux)); + if (mp4v_demux_open(filepath, &demux, + probe_video_sample, 1, + probe_video_chunk, 1, NULL, 0, + probe_video_sync, 1) < 0) + { + rb->splash(HZ * 2, "Invalid MP4/M4V file"); + goto cleanup; + } + if (demux.format != MAKEFOURCC('a', 'v', 'c', '1') || + demux.avc_profile != 66 || demux.avc_level > VIDEO_MAX_LEVEL || + demux.width == 0 || demux.height == 0 || + demux.width > VIDEO_MAX_WIDTH || demux.height > VIDEO_MAX_HEIGHT || + demux.nalu_len_size < 1 || demux.nalu_len_size > 4) + { + rb->splash(HZ * 3, "Need H.264 Baseline <= L3.0\nMax 640x480"); + goto cleanup; + } + if (!video_tables_sane(&demux)) + { + rb->splash(HZ * 3, "Movie tables exceed safe limit"); + goto cleanup; + } + + pool.cursor = buffer; + pool.end = (uint8_t *)buffer + buffer_size; + video_samples = video_pool_take( + &pool, demux.num_samples * sizeof(*video_samples), 32); + video_chunks = video_pool_take( + &pool, demux.num_stco * sizeof(*video_chunks), 32); + video_stsc = video_pool_take( + &pool, demux.num_stsc * sizeof(*video_stsc), 32); + video_sync = demux.num_stss > 0 ? video_pool_take( + &pool, demux.num_stss * sizeof(*video_sync), 32) : NULL; + decoder_size = rb->hw_h264->buf_size( + (demux.width + 15) & ~15, (demux.height + 15) & ~15); + decoder_buffer = video_pool_take(&pool, decoder_size, 4096); + read_buffer = video_pool_take(&pool, VIDEO_READ_BUFFER, 32); + scale_buffer = video_pool_take(&pool, VIDEO_OUTPUT_BUFFER, 32); + audio_codec_workspace_size = demux.audio_num_samples > 0 ? + video_audio_workspace_size(&demux) : 0; + audio_codec_workspace = audio_codec_workspace_size > 0 ? + video_pool_take(&pool, audio_codec_workspace_size, + CACHEALIGN_SIZE) : NULL; + pcm_buffer = audio_codec_workspace_size > 0 ? + video_pool_take(&pool, VIDEO_PCM_BUFFER, CACHEALIGN_SIZE) : NULL; + if (video_samples == NULL || video_chunks == NULL || + video_stsc == NULL || + (demux.num_stss > 0 && video_sync == NULL) || + decoder_buffer == NULL || read_buffer == NULL || + scale_buffer == NULL || + (demux.audio_num_samples > 0 && + (audio_codec_workspace == NULL || pcm_buffer == NULL))) + { + rb->splash(HZ * 2, "Not enough video memory"); + goto cleanup; + } + rb->splash(0, "Loading movie index..."); + if (mp4v_demux_open(filepath, &demux, + video_samples, demux.num_samples, + video_chunks, demux.num_stco, + video_stsc, demux.num_stsc, + video_sync, demux.num_stss) < 0 || + !video_tables_sane(&demux) || !video_stsc_sane(&demux)) + { + rb->splash(HZ * 2, "MP4 table parse failed"); + goto cleanup; + } + duration_ms = video_duration_ms(&demux); + sample = video_resume_load(filepath, demux.num_samples); + while (sample > 0 && !mp4v_is_keyframe(&demux, sample)) + sample--; + video_timing_for_sample(&demux, sample, &timing); + + /* Preparation, decode, and LCD presentation need the 108 MHz bus clock. + * Keep one boost reference from table parsing through active playback. */ + rb->splash(0, "Starting video..."); + decoder = rb->hw_h264->open( + decoder_buffer, decoder_size, + (demux.width + 15) & ~15, (demux.height + 15) & ~15); + if (decoder == NULL || + rb->hw_h264->configure(decoder, demux.codecdata, + demux.codecdata_len) < 0) + { + rb->splash(HZ * 2, "H.264 decoder init failed"); + goto cleanup; + } + video_fd = rb->open(filepath, O_RDONLY); + if (video_fd < 0) + goto cleanup; + + rb->pcmbuf_fade(false, true); + rb->sound_set(SOUND_VOLUME, rb->global_status->volume); + start_tick = (*rb->current_tick) - + (long)((uint64_t)video_pts_ms(&demux, &timing) * HZ / 1000u); + overlay_until = (*rb->current_tick) + VIDEO_OSD_SHOW_TICKS; + if (demux.audio_format == MAKEFOURCC('m', 'p', '4', 'a') && + demux.audio_codecdata_len > 0 && + video_audio_init(filepath, &demux, audio_codec_workspace, + audio_codec_workspace_size, pcm_buffer, + VIDEO_PCM_BUFFER) == 0) + { + int wait = 0; + uint32_t initial_ms = video_pts_ms(&demux, &timing); + + have_audio = true; + audio_master = true; + video_audio_play(); + if (initial_ms > 0) + video_audio_seek(initial_ms); + while (!video_audio_ready() && wait < HZ && action == VIDEO_INPUT_NONE) + { + bool redraw_requested; + + action = video_input(&paused, &start_tick, + &pause_started, true, &cpu_boosted, + &overlay_until, &redraw_requested); + if (action == VIDEO_INPUT_MENU) + action = video_show_menu( + &paused, &start_tick, &pause_started, true, + &cpu_boosted, &overlay_until, &redraw_requested); + rb->sleep(1); + wait++; + } + if (video_audio_failed()) + { + video_audio_stop(); + have_audio = false; + audio_master = false; + } + } + + rb->lcd_set_foreground(LCD_BLACK); + rb->lcd_clear_display(); + rb->lcd_update(); + rb->backlight_on(); + rb->backlight_set_timeout(0); + while (sample < demux.num_samples && action == VIDEO_INPUT_NONE) + { + uint32_t offset; + uint32_t size; + uint32_t pts_ms = video_pts_ms(&demux, &timing); + bool seek_requested = false; + int decoded; + + while (action == VIDEO_INPUT_NONE) + { + uint32_t clock_ms; + bool redraw_requested; + + action = video_input(&paused, &start_tick, + &pause_started, have_audio, &cpu_boosted, + &overlay_until, &redraw_requested); + if (action == VIDEO_INPUT_MENU) + action = video_show_menu( + &paused, &start_tick, &pause_started, have_audio, + &cpu_boosted, &overlay_until, &redraw_requested); + if (redraw_requested && have_frame) + { + video_redraw_frame(decoder, scale_buffer, paused, + displayed_ms, duration_ms, true); + overlay_drawn = true; + } + if (action == VIDEO_INPUT_SEEK_BACK || + action == VIDEO_INPUT_SEEK_FORWARD) + { + int64_t target = + (int64_t)(audio_master ? video_pcm_get_clock_ms() : + pts_ms) + + (action == VIDEO_INPUT_SEEK_BACK ? -10000 : 10000); + + if (target < 0) + target = 0; + if ((uint64_t)target >= duration_ms && duration_ms > 0) + target = duration_ms - 1; + sample = video_sample_for_ms(&demux, (uint32_t)target, + &timing); + pts_ms = video_pts_ms(&demux, &timing); + rb->hw_h264->close(decoder); + decoder = rb->hw_h264->open( + decoder_buffer, decoder_size, + (demux.width + 15) & ~15, + (demux.height + 15) & ~15); + if (decoder == NULL || + rb->hw_h264->configure(decoder, demux.codecdata, + demux.codecdata_len) < 0) + { + rb->splash(HZ * 2, "H.264 seek failed"); + decoder = NULL; + goto cleanup; + } + if (have_audio) + video_audio_seek(pts_ms); + start_tick = (*rb->current_tick) - + (long)((uint64_t)pts_ms * HZ / 1000u); + overlay_until = (*rb->current_tick) + VIDEO_OSD_SHOW_TICKS; + action = VIDEO_INPUT_NONE; + seek_requested = true; + break; + } + if (action != VIDEO_INPUT_NONE) + break; + if (paused) + { + if (overlay_drawn && + !TIME_BEFORE((*rb->current_tick), overlay_until)) + { + video_redraw_frame(decoder, scale_buffer, true, + displayed_ms, duration_ms, false); + overlay_drawn = false; + } + rb->sleep(1); + continue; + } + clock_ms = audio_master ? video_pcm_get_clock_ms() : + (uint32_t)(((*rb->current_tick) - start_tick) * 1000 / HZ); + if (audio_master && + (video_audio_failed() || + (!video_audio_is_active() && video_pcm_empty()))) + { + video_audio_stop(); + have_audio = false; + audio_master = false; + start_tick = (*rb->current_tick) - + (long)((uint64_t)clock_ms * HZ / 1000u); + } + if (clock_ms + 2 >= pts_ms) + break; + rb->sleep(1); + } + if (action != VIDEO_INPUT_NONE) + break; + if (seek_requested) + continue; + if (mp4v_get_sample_offset(&demux, sample, &offset, &size) < 0 || + size == 0 || size > VIDEO_READ_BUFFER || + rb->lseek(video_fd, offset, SEEK_SET) < 0 || + rb->read(video_fd, read_buffer, size) != (ssize_t)size) + { + rb->splash(HZ * 2, "Video sample read failed"); + goto cleanup; + } + decoded = rb->hw_h264->decode_sample( + decoder, read_buffer, size, demux.nalu_len_size); + if (decoded < 0) + { + rb->splash(HZ * 2, "VPU sample decode failed"); + goto cleanup; + } + if (decoded > 0) + { + const uint8_t *frame_y; + const uint8_t *frame_cb; + const uint8_t *frame_cr; + int width; + int height; + int stride; + bool overlay_visible = + TIME_BEFORE((*rb->current_tick), overlay_until); + + rb->hw_h264->get_frame(decoder, &frame_y, &frame_cb, &frame_cr, + &width, &height, &stride); + video_draw_frame(frame_y, frame_cb, frame_cr, width, height, + stride, scale_buffer, paused, pts_ms, + duration_ms, overlay_visible); + displayed_ms = pts_ms; + have_frame = true; + overlay_drawn = overlay_visible; + } + last_sample = sample; + video_timing_advance(&demux, &timing); + sample++; + } + result = action == VIDEO_INPUT_USB ? 2 : + (action == VIDEO_INPUT_EXIT ? 1 : 0); + if (result == 0) + video_resume_clear(filepath); + else + video_resume_save(filepath, last_sample, demux.num_samples); + +cleanup: + if (have_audio) + video_audio_stop(); + if (video_fd >= 0) + rb->close(video_fd); + if (decoder != NULL) + rb->hw_h264->close(decoder); + rb->pcmbuf_fade(false, false); + rb->backlight_set_timeout(rb->global_settings->backlight_timeout); + rb->lcd_set_foreground(LCD_BLACK); + rb->lcd_clear_display(); + rb->lcd_update(); + if (cpu_boosted) + rb->cpu_boost(false); + return result; +} diff --git a/apps/plugins/h264_player/video_playback.h b/apps/plugins/h264_player/video_playback.h new file mode 100644 index 0000000000..351e0cad4c --- /dev/null +++ b/apps/plugins/h264_player/video_playback.h @@ -0,0 +1,34 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2025-2026 David Cormier + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#ifndef VIDEO_PLAYBACK_H +#define VIDEO_PLAYBACK_H + +/* + * Hardware H.264 player implementation. + */ + +#include + +/* Play a validated H.264 Baseline/AAC-LC MP4 from caller-owned memory. + * The caller must retain the plugin audio buffer until this function returns. + * Returns 0 at end of stream, 1 on user exit, 2 for USB, or -1 on failure. */ +int video_h264_play(const char *filepath, void *buffer, size_t buffer_size); + +#endif /* VIDEO_PLAYBACK_H */ diff --git a/apps/plugins/imageviewer/imageviewer_button.h b/apps/plugins/imageviewer/imageviewer_button.h index d86f4c812e..7eeb41df86 100644 --- a/apps/plugins/imageviewer/imageviewer_button.h +++ b/apps/plugins/imageviewer/imageviewer_button.h @@ -544,7 +544,7 @@ #define IMGVIEW_MENU (BUTTON_PLAY|BUTTON_REPEAT) #define IMGVIEW_SLIDE_SHOW BUTTON_PLAY -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD /* use touchscreen */ #elif CONFIG_KEYPAD == RG_NANO_PAD @@ -559,7 +559,7 @@ #define IMGVIEW_MENU BUTTON_B #define IMGVIEW_QUIT BUTTON_START -#elif CONFIG_KEYPAD == CTRU_PAD +#elif CONFIG_KEYPAD == N3DS_PAD #define IMGVIEW_ZOOM_IN BUTTON_POWER #define IMGVIEW_ZOOM_OUT BUTTON_USER #define IMGVIEW_UP BUTTON_UP @@ -571,6 +571,18 @@ #define IMGVIEW_MENU BUTTON_MENU #define IMGVIEW_QUIT BUTTON_BACK +#elif CONFIG_KEYPAD == ECHO_R1_PAD +#define IMGVIEW_ZOOM_IN BUTTON_A +#define IMGVIEW_ZOOM_OUT BUTTON_B +#define IMGVIEW_UP BUTTON_UP +#define IMGVIEW_DOWN BUTTON_DOWN +#define IMGVIEW_LEFT BUTTON_LEFT +#define IMGVIEW_RIGHT BUTTON_RIGHT +#define IMGVIEW_NEXT BUTTON_Y +#define IMGVIEW_PREVIOUS BUTTON_X +#define IMGVIEW_MENU BUTTON_START +#define IMGVIEW_QUIT BUTTON_POWER + #else #error No keymap defined! #endif diff --git a/apps/plugins/imageviewer/jpeg/jpeg.c b/apps/plugins/imageviewer/jpeg/jpeg.c index e8d654c00b..ea13ca7512 100644 --- a/apps/plugins/imageviewer/jpeg/jpeg.c +++ b/apps/plugins/imageviewer/jpeg/jpeg.c @@ -91,21 +91,22 @@ static void draw_image_rect(struct image_info *info, static int img_mem(int ds) { - int size; + /* in 64 bits: a huge image overflows an int and would seem to fit */ + long long size; struct jpeg *p_jpg = &jpg; - size = (p_jpg->x_phys/ds/p_jpg->subsample_x[0]) + size = (long long)(p_jpg->x_phys/ds/p_jpg->subsample_x[0]) * (p_jpg->y_phys/ds/p_jpg->subsample_y[0]); #ifdef HAVE_LCD_COLOR if (p_jpg->blocks > 1) /* colour, add requirements for chroma */ { - size += (p_jpg->x_phys/ds/p_jpg->subsample_x[1]) + size += (long long)(p_jpg->x_phys/ds/p_jpg->subsample_x[1]) * (p_jpg->y_phys/ds/p_jpg->subsample_y[1]); - size += (p_jpg->x_phys/ds/p_jpg->subsample_x[2]) + size += (long long)(p_jpg->x_phys/ds/p_jpg->subsample_x[2]) * (p_jpg->y_phys/ds/p_jpg->subsample_y[2]); } #endif - return size; + return size > INT_MAX ? INT_MAX : size; } static int load_image(char *filename, struct image_info *info, @@ -133,7 +134,7 @@ static int load_image(char *filename, struct image_info *info, } else { - filesize = rb->filesize(fd); + filesize = rb->ffilesize(fd); } /* allocate JPEG buffer */ @@ -177,7 +178,7 @@ static int load_image(char *filename, struct image_info *info, /* process markers, unstuffing */ status = process_markers(buf_jpeg, filesize, p_jpg); - if (status < 0 || (status & (DQT | SOF0)) != (DQT | SOF0)) + if (status < 0 || (status & (DQT | SOF0 | SOS)) != (DQT | SOF0 | SOS)) { /* bad format or minimum components not contained */ #ifndef HAVE_LCD_COLOR rb->splashf(HZ, "unsupported %d", status); diff --git a/apps/plugins/imageviewer/jpeg/jpeg_decoder.c b/apps/plugins/imageviewer/jpeg/jpeg_decoder.c index a06dc8ca1a..39da52cc30 100644 --- a/apps/plugins/imageviewer/jpeg/jpeg_decoder.c +++ b/apps/plugins/imageviewer/jpeg/jpeg_decoder.c @@ -494,12 +494,23 @@ int process_markers(unsigned char* p_src, long size, struct jpeg* p_jpeg) while (p_src < p_end) { + int marker; if (*p_src++ != 0xFF) /* no marker? */ { continue; /* discard */ } + if (p_src >= p_end) + break; + marker = *p_src++; + if (marker != 0xFF && marker != 0x00 && marker != 0x01 + && (marker < 0xD0 || marker > 0xD9)) + { /* a marker segment: it must lie within the file */ + if (p_end - p_src < 2 || (p_src[0] << 8 | p_src[1]) < 2 + || (p_src[0] << 8 | p_src[1]) > p_end - p_src) + return -13; /* segment runs past the end of the file */ + } - switch (*p_src++) + switch (marker) { case 0xFF: /* Previous FF was fill byte */ p_src--; /* This FF could be start of a marker */ @@ -508,6 +519,7 @@ int process_markers(unsigned char* p_src, long size, struct jpeg* p_jpeg) break; case 0xC0: /* SOF Huff - Baseline DCT */ + case 0xC1: /* SOF Huff - Extended sequential DCT (8 bit samples) */ { ret |= SOF0; marker_size = *p_src++ << 8; /* Highbyte */ @@ -533,15 +545,31 @@ int process_markers(unsigned char* p_src, long size, struct jpeg* p_jpeg) p_jpeg->frameheader[i].horizontal_sampling = *p_src >> 4; p_jpeg->frameheader[i].vertical_sampling = *p_src++ & 0x0F; p_jpeg->frameheader[i].quanttable_select = *p_src++; + if (p_jpeg->frameheader[i].quanttable_select > 3) + return -8; /* Unsupported quantization table */ if (p_jpeg->frameheader[i].horizontal_sampling > 2 || p_jpeg->frameheader[i].vertical_sampling > 2) return -3; /* Unsupported SOF0 subsampling */ } p_jpeg->blocks = n; + for (i=1; iframeheader[i].horizontal_sampling != 1 + || p_jpeg->frameheader[i].vertical_sampling != 1) + return -3; /* Unsupported SOF0 subsampling */ + } + if (p_jpeg->x_size == 0 || p_jpeg->y_size == 0) + return -12; /* Height defined by DNL not supported */ + /* A single-component scan is non-interleaved: the MCU is one + 8x8 block regardless of the sampling factors (T.81 A.2.2) */ + if (n == 1) + { + p_jpeg->frameheader[0].horizontal_sampling = 1; + p_jpeg->frameheader[0].vertical_sampling = 1; + } } break; - case 0xC1: /* SOF Huff - Extended sequential DCT*/ case 0xC2: /* SOF Huff - Progressive DCT*/ case 0xC3: /* SOF Huff - Spatial (sequential) lossless*/ case 0xC5: /* SOF Huff - Differential sequential DCT*/ @@ -582,7 +610,8 @@ int process_markers(unsigned char* p_src, long size, struct jpeg* p_jpeg) sum += *p_src; p_jpeg->hufftable[i].huffmancodes_ac[j] = *p_src++; } - if(16 + sum > AC_LEN) + if(16 + sum > AC_LEN + || p_src + sum > p_temp + marker_size - 2) return -10; /* longer than allowed */ for (; j < 16 + sum; j++) @@ -595,7 +624,8 @@ int process_markers(unsigned char* p_src, long size, struct jpeg* p_jpeg) sum += *p_src; p_jpeg->hufftable[i].huffmancodes_dc[j] = *p_src++; } - if(16 + sum > DC_LEN) + if(16 + sum > DC_LEN + || p_src + sum > p_temp + marker_size - 2) return -11; /* longer than allowed */ for (; j < 16 + sum; j++) @@ -622,16 +652,38 @@ int process_markers(unsigned char* p_src, long size, struct jpeg* p_jpeg) marker_size |= *p_src++; /* Lowbyte */ n = (marker_size-2-1-3)/2; - if (*p_src++ != n || (n != 1 && n != 3)) + if (*p_src++ != n || (n != 1 && n != 3) + /* one scan with all components; multi-scan files and + SOS before SOF are not supported (blocks = Nf here) */ + || n != p_jpeg->blocks) { return (-7); /* Unsupported SOS component specification */ } for (i=0; iscanheader[i].ID = *p_src++; + if (p_jpeg->scanheader[i].ID != p_jpeg->frameheader[i].ID) + { + return (-7); /* components out of frame order */ + } p_jpeg->scanheader[i].DC_select = *p_src >> 4; p_jpeg->scanheader[i].AC_select = *p_src++ & 0x0F; + if (p_jpeg->scanheader[i].DC_select > 1 + || p_jpeg->scanheader[i].AC_select > 1) + { + return (-5); /* Huffman table index out of range */ + } } + p_jpeg->rgb = n == 3 && jpeg_is_rgb(p_jpeg->jfif, p_jpeg->adobe, + p_jpeg->frameheader[0].ID, p_jpeg->frameheader[1].ID, + p_jpeg->frameheader[2].ID); + /* RGB needs every component in one block per MCU: the + planes are converted to YCbCr in place (colour) or + combined per block (greyscale) */ + if (p_jpeg->rgb + && (p_jpeg->frameheader[0].horizontal_sampling != 1 + || p_jpeg->frameheader[0].vertical_sampling != 1)) + return -3; /* Unsupported SOF0 subsampling */ p_src += 3; /* skip spectral information */ p_jpeg->p_entropy_data = p_src; p_end = p_src; /* exit while loop */ @@ -643,18 +695,27 @@ int process_markers(unsigned char* p_src, long size, struct jpeg* p_jpeg) ret |= DQT; marker_size = *p_src++ << 8; /* Highbyte */ marker_size |= *p_src++; /* Lowbyte */ - n = (marker_size-2)/(QUANT_TABLE_LENGTH+1); /* # of tables */ - for (i=0; i QUANT_TABLE_LENGTH) /* a table */ { - int id = *p_src++; /* ID */ - if (id >= 4) + int id = *p_src++; /* Pq: precision, Tq: ID */ + int pq = id >> 4; + id &= 0x0F; + if (id >= 4 || pq > 1 + || p_seg_end - p_src < QUANT_TABLE_LENGTH * (pq + 1)) { return (-8); /* Unsupported quantization table */ } /* Read Quantisation table: */ for (j=0; jquanttable[id][j] = *p_src++; + { + int q = *p_src++; + if (pq) /* 16 bit entries (SOF1) */ + q = q << 8 | *p_src++; + p_jpeg->quanttable[id][j] = q; + } } + p_src = p_seg_end; } break; @@ -671,7 +732,6 @@ int process_markers(unsigned char* p_src, long size, struct jpeg* p_jpeg) case 0xDC: /* Define Number of Lines */ case 0xDE: /* Define Hierarchical progression */ case 0xDF: /* Expand Reference Component(s) */ - case 0xE0: /* Application Field 0*/ case 0xE1: /* Application Field 1*/ case 0xE2: /* Application Field 2*/ case 0xE3: /* Application Field 3*/ @@ -685,7 +745,6 @@ int process_markers(unsigned char* p_src, long size, struct jpeg* p_jpeg) case 0xEB: /* Application Field 11*/ case 0xEC: /* Application Field 12*/ case 0xED: /* Application Field 13*/ - case 0xEE: /* Application Field 14*/ case 0xEF: /* Application Field 15*/ case 0xFE: /* Comment */ { @@ -695,6 +754,19 @@ int process_markers(unsigned char* p_src, long size, struct jpeg* p_jpeg) } break; + case 0xE0: /* Application Field 0 (JFIF) */ + case 0xEE: /* Application Field 14 (Adobe) */ + { + int marker = p_src[-1]; + marker_size = *p_src++ << 8; /* Highbyte */ + marker_size |= *p_src++; /* Lowbyte */ + n = MIN(marker_size - 2, p_end - p_src); + jpeg_app_colorspace(marker, p_src, MIN(n, 12), &p_jpeg->jfif, + &p_jpeg->adobe); + p_src += marker_size-2; /* skip segment */ + } + break; + case 0xF0: /* Reserved for JPEG extensions */ case 0xF1: /* Reserved for JPEG extensions */ case 0xF2: /* Reserved for JPEG extensions */ @@ -878,7 +950,7 @@ static const int zag[] = void build_lut(struct jpeg* p_jpeg) { - int i; + int i, c; fix_huff_tbl(p_jpeg->hufftable[0].huffmancodes_dc, &p_jpeg->dc_derived_tbls[0]); fix_huff_tbl(p_jpeg->hufftable[0].huffmancodes_ac, @@ -888,11 +960,14 @@ void build_lut(struct jpeg* p_jpeg) fix_huff_tbl(p_jpeg->hufftable[1].huffmancodes_ac, &p_jpeg->ac_derived_tbls[1]); - /* build the dequantization tables for the IDCT (De-ZiZagged) */ - for (i=0; i<64; i++) + /* build each component's dequantization table for the IDCT + (De-ZiZagged); p_jpeg->blocks is still the component count here */ + for (c=0; cblocks; c++) { - p_jpeg->qt_idct[0][zag[i]] = p_jpeg->quanttable[0][i]; - p_jpeg->qt_idct[1][zag[i]] = p_jpeg->quanttable[1][i]; + const int* qt = p_jpeg->quanttable[ + p_jpeg->frameheader[c].quanttable_select]; + for (i=0; i<64; i++) + p_jpeg->qt_idct[c][zag[i]] = qt[i]; } for (i=0; i<4; i++) @@ -911,10 +986,6 @@ void build_lut(struct jpeg* p_jpeg) p_jpeg->mcu_membership[1] = 0; p_jpeg->mcu_membership[2] = 1; p_jpeg->mcu_membership[3] = 2; - p_jpeg->tab_membership[0] = 0; /* DC, DC, AC, AC */ - p_jpeg->tab_membership[1] = 0; - p_jpeg->tab_membership[2] = 1; - p_jpeg->tab_membership[3] = 1; p_jpeg->subsample_x[0] = 1; p_jpeg->subsample_x[1] = 2; p_jpeg->subsample_x[2] = 2; @@ -922,7 +993,7 @@ void build_lut(struct jpeg* p_jpeg) p_jpeg->subsample_y[1] = 1; p_jpeg->subsample_y[2] = 1; } - if (p_jpeg->frameheader[0].horizontal_sampling == 1 + else if (p_jpeg->frameheader[0].horizontal_sampling == 1 && p_jpeg->frameheader[0].vertical_sampling == 2) { /* 4:2:2 vertically subsampled */ p_jpeg->store_pos[1] = 2; /* block positions are mirrored */ @@ -936,10 +1007,6 @@ void build_lut(struct jpeg* p_jpeg) p_jpeg->mcu_membership[1] = 0; p_jpeg->mcu_membership[2] = 1; p_jpeg->mcu_membership[3] = 2; - p_jpeg->tab_membership[0] = 0; /* DC, DC, AC, AC */ - p_jpeg->tab_membership[1] = 0; - p_jpeg->tab_membership[2] = 1; - p_jpeg->tab_membership[3] = 1; p_jpeg->subsample_x[0] = 1; p_jpeg->subsample_x[1] = 1; p_jpeg->subsample_x[2] = 1; @@ -961,12 +1028,6 @@ void build_lut(struct jpeg* p_jpeg) p_jpeg->mcu_membership[3] = 0; p_jpeg->mcu_membership[4] = 1; p_jpeg->mcu_membership[5] = 2; - p_jpeg->tab_membership[0] = 0; - p_jpeg->tab_membership[1] = 0; - p_jpeg->tab_membership[2] = 0; - p_jpeg->tab_membership[3] = 0; - p_jpeg->tab_membership[4] = 1; - p_jpeg->tab_membership[5] = 1; p_jpeg->subsample_x[0] = 1; p_jpeg->subsample_x[1] = 2; p_jpeg->subsample_x[2] = 2; @@ -985,9 +1046,6 @@ void build_lut(struct jpeg* p_jpeg) p_jpeg->mcu_membership[0] = 0; p_jpeg->mcu_membership[1] = 1; p_jpeg->mcu_membership[2] = 2; - p_jpeg->tab_membership[0] = 0; - p_jpeg->tab_membership[1] = 1; - p_jpeg->tab_membership[2] = 1; p_jpeg->subsample_x[0] = 1; p_jpeg->subsample_x[1] = 1; p_jpeg->subsample_x[2] = 1; @@ -1019,26 +1077,31 @@ void build_lut(struct jpeg* p_jpeg) * is evaluated multiple times. */ +/* Fetch the next entropy-coded byte. Past the end of the data this returns + * zero without reading, but still advances so the caller's end check stops + * the decode. */ +INLINE unsigned char fetch_byte(struct bitstream* pb) +{ + unsigned char byte = 0; + + if (pb->next_input_byte < pb->input_end) + { + byte = *pb->next_input_byte; + if (byte == 0xFF) /* legal marker can be byte stuffing or RSTm */ + { /* simplification: just skip the (one-byte) marker code */ + pb->next_input_byte++; + } + } + pb->next_input_byte++; + return byte; +} + INLINE void check_bit_buffer(struct bitstream* pb, int nbits) { if (pb->bits_left < nbits) { /* nbits is <= 16, so I can always refill 2 bytes in this case */ - unsigned char byte; - - byte = *pb->next_input_byte++; - if (byte == 0xFF) /* legal marker can be byte stuffing or RSTm */ - { /* simplification: just skip the (one-byte) marker code */ - pb->next_input_byte++; - } - pb->get_buffer = (pb->get_buffer << 8) | byte; - - byte = *pb->next_input_byte++; - if (byte == 0xFF) /* legal marker can be byte stuffing or RSTm */ - { /* simplification: just skip the (one-byte) marker code */ - pb->next_input_byte++; - } - pb->get_buffer = (pb->get_buffer << 8) | byte; - + pb->get_buffer = (pb->get_buffer << 8) | fetch_byte(pb); + pb->get_buffer = (pb->get_buffer << 8) | fetch_byte(pb); pb->bits_left += 16; } } @@ -1269,9 +1332,10 @@ int jpeg_decode(struct jpeg* p_jpeg, unsigned char* p_pixel[3], int k = 1; /* coefficient index */ int s, r; /* huffman values */ int ci = p_jpeg->mcu_membership[blkn]; /* component index */ - int ti = p_jpeg->tab_membership[blkn]; /* table index */ - struct derived_tbl* dctbl = &p_jpeg->dc_derived_tbls[ti]; - struct derived_tbl* actbl = &p_jpeg->ac_derived_tbls[ti]; + struct derived_tbl* dctbl = + &p_jpeg->dc_derived_tbls[p_jpeg->scanheader[ci].DC_select]; + struct derived_tbl* actbl = + &p_jpeg->ac_derived_tbls[p_jpeg->scanheader[ci].AC_select]; /* Section F.2.2.1: decode the DC coefficient difference */ s = huff_decode_dc(&bs, dctbl); @@ -1330,11 +1394,11 @@ int jpeg_decode(struct jpeg* p_jpeg, unsigned char* p_pixel[3], if (ci == 0) { /* Y component needs to bother about block store */ pf_idct(p_byte[0]+store_offs[blkn], block, - p_jpeg->qt_idct[ti], skip_line[0]); + p_jpeg->qt_idct[ci], skip_line[0]); } else { /* chroma */ - pf_idct(p_byte[ci], block, p_jpeg->qt_idct[ti], + pf_idct(p_byte[ci], block, p_jpeg->qt_idct[ci], skip_line[ci]); } } /* for blkn */ @@ -1353,6 +1417,20 @@ int jpeg_decode(struct jpeg* p_jpeg, unsigned char* p_pixel[3], pf_progress(y, p_jpeg->y_mbl-1); /* notify about decoding progress */ } /* for y */ + if (p_jpeg->rgb) + { /* the display expects YCbCr: convert the equal sized R, G and B + planes in place (JFIF equations) */ + unsigned char *pr = p_pixel[0], *pg = p_pixel[1], *pb = p_pixel[2]; + unsigned char *end = pr + width * height; + for (; pr < end; pr++, pg++, pb++) + { + int r = *pr, g = *pg, b = *pb; + *pr = (77 * r + 150 * g + 29 * b + 128) >> 8; + *pg = clamp_component((-43 * r - 85 * g + 128 * b + 32896) >> 8); + *pb = clamp_component((128 * r - 107 * g - 21 * b + 32896) >> 8); + } + } + return 0; /* success */ } #else /* !HAVE_LCD_COLOR */ @@ -1377,7 +1455,8 @@ int jpeg_decode(struct jpeg* p_jpeg, unsigned char* p_pixel[1], int downscale, int k_need; /* AC coefficients needed up to here */ int zero_need; /* init the block with this many zeros */ - int last_dc_val = 0; + int last_dc_val[3] = {0, 0, 0}; + unsigned char rgb_tmp[2][64]; /* R and G of an RGB MCU */ int store_offs[4]; /* memory offsets: order of Y11 Y12 Y21 Y22 U V */ int restart = p_jpeg->restart_interval; /* MCUs until restart marker */ @@ -1440,17 +1519,18 @@ int jpeg_decode(struct jpeg* p_jpeg, unsigned char* p_pixel[1], int downscale, int k = 1; /* coefficient index */ int s, r; /* huffman values */ int ci = p_jpeg->mcu_membership[blkn]; /* component index */ - int ti = p_jpeg->tab_membership[blkn]; /* table index */ - struct derived_tbl* dctbl = &p_jpeg->dc_derived_tbls[ti]; - struct derived_tbl* actbl = &p_jpeg->ac_derived_tbls[ti]; + struct derived_tbl* dctbl = + &p_jpeg->dc_derived_tbls[p_jpeg->scanheader[ci].DC_select]; + struct derived_tbl* actbl = + &p_jpeg->ac_derived_tbls[p_jpeg->scanheader[ci].AC_select]; /* Section F.2.2.1: decode the DC coefficient difference */ s = huff_decode_dc(&bs, dctbl); - if (ci == 0) /* only for Y component */ + if (ci == 0 || p_jpeg->rgb) /* Y, or all of R, G, B */ { - last_dc_val += s; - block[0] = last_dc_val; /* output it (assumes zag[0] = 0) */ + last_dc_val[ci] += s; + block[0] = last_dc_val[ci]; /* output it (zag[0] = 0) */ /* coefficient buffer must be cleared */ MEMSET(block+1, 0, zero_need*sizeof(block[0])); @@ -1501,18 +1581,35 @@ int jpeg_decode(struct jpeg* p_jpeg, unsigned char* p_pixel[1], int downscale, } } /* for k */ - if (ci == 0) + if (ci == 0 && !p_jpeg->rgb) { /* only for Y component */ - pf_idct(p_byte+store_offs[blkn], block, p_jpeg->qt_idct[ti], + pf_idct(p_byte+store_offs[blkn], block, p_jpeg->qt_idct[ci], skip_line); } + else if (p_jpeg->rgb && ci < 2) + { /* keep R and G until B is decoded */ + pf_idct(rgb_tmp[ci], block, p_jpeg->qt_idct[ci], 8); + } + else if (p_jpeg->rgb) + { /* luma from R, G and B (JFIF weights) */ + int n = 8 / downscale; + int xi, yi; + unsigned char *p = p_byte; + pf_idct(p, block, p_jpeg->qt_idct[ci], skip_line); + for (yi = 0; yi < n; yi++, p += skip_line) + for (xi = 0; xi < n; xi++) + p[xi] = (77 * rgb_tmp[0][yi * 8 + xi] + + 150 * rgb_tmp[1][yi * 8 + xi] + + 29 * p[xi] + 128) >> 8; + } } /* for blkn */ p_byte += skip_mcu; if (p_jpeg->restart_interval && --restart == 0) { /* if a restart marker is due: */ restart = p_jpeg->restart_interval; /* count again */ search_restart(&bs); /* align the bitstream */ - last_dc_val = 0; /* reset decoder */ + last_dc_val[0] = last_dc_val[1] = + last_dc_val[2] = 0; /* reset decoder */ } } /* for x */ if (pf_progress != NULL) diff --git a/apps/plugins/imageviewer/jpeg/jpeg_decoder.h b/apps/plugins/imageviewer/jpeg/jpeg_decoder.h index 853c1ebbf5..307b57ff4a 100644 --- a/apps/plugins/imageviewer/jpeg/jpeg_decoder.h +++ b/apps/plugins/imageviewer/jpeg/jpeg_decoder.h @@ -37,22 +37,24 @@ struct jpeg int blocks; /* blocks per MB */ int restart_interval; /* number of MCUs between RSTm markers */ int store_pos[4]; /* for Y block ordering */ + bool jfif; /* saw a JFIF APP0 marker */ + unsigned char adobe; /* Adobe APP14 transform flag + 1, 0 if none */ + bool rgb; /* the components are R, G, B rather than Y, Cb, Cr */ unsigned char* p_entropy_data; unsigned char* p_entropy_end; int quanttable[4][QUANT_TABLE_LENGTH]; /* raw quantization tables 0-3 */ - int qt_idct[2][QUANT_TABLE_LENGTH]; /* quantization tables for IDCT */ + int qt_idct[3][QUANT_TABLE_LENGTH]; /* per component, for IDCT */ struct huffman_table hufftable[2]; /* Huffman tables */ struct derived_tbl dc_derived_tbls[2]; /* Huffman-LUTs */ struct derived_tbl ac_derived_tbls[2]; struct frame_component frameheader[3]; /* Component descriptor */ - struct scan_component scanheader[3]; /* currently not used */ + struct scan_component scanheader[3]; /* Huffman tables per component */ int mcu_membership[6]; /* info per block */ - int tab_membership[6]; int subsample_x[3]; /* info per component */ int subsample_y[3]; }; diff --git a/apps/plugins/imageviewer/jpegp/BUFFILEGETC.c b/apps/plugins/imageviewer/jpegp/BUFFILEGETC.c index a833851b1b..47a5087ef0 100644 --- a/apps/plugins/imageviewer/jpegp/BUFFILEGETC.c +++ b/apps/plugins/imageviewer/jpegp/BUFFILEGETC.c @@ -9,14 +9,25 @@ static unsigned char buff[256]; //TODO: Adjust it... static int length = 0; static int cur_buff_pos = 0; static int file_pos = 0; +/* Past the end of the file. Overlays are loaded without clearing their + .bss, so these are set in OPEN() rather than relied on to start at 0. */ +static bool at_eof; /* read() found the end: don't call it again */ +static unsigned eof_bytes; /* bytes fed since: alternate FF, D9 */ extern int GETC(void) { if (cur_buff_pos >= length) { - length = rb->read(fd, buff, sizeof(buff)); - file_pos += length; + length = at_eof ? 0 : rb->read(fd, buff, sizeof(buff)); cur_buff_pos = 0; + if (length <= 0) + { /* past the end of a damaged file: feed EOI markers (FF D9) + so every loop in the decoder ends instead of spinning */ + at_eof = true; + length = 0; + return (eof_bytes++ & 1) ? 0xD9 : 0xFF; + } + file_pos += length; } return buff[cur_buff_pos++]; @@ -56,11 +67,13 @@ extern void SEEK(int d) } file_pos = rb->lseek(fd, (cur_buff_pos - length) + d, SEEK_CUR); cur_buff_pos = length = 0; + at_eof = false; } extern void POS(int d) { cur_buff_pos = length = 0; + at_eof = false; file_pos = d; rb->lseek(fd, d, SEEK_SET); } @@ -77,6 +90,8 @@ extern void *OPEN(char *f) memset(buff, 0, sizeof(buff)); printf("Opening %s\n", f); cur_buff_pos = length = file_pos = 0; + at_eof = false; + eof_bytes = 0; fd = rb->open(f,O_RDONLY); if ( fd < 0 ) diff --git a/apps/plugins/imageviewer/jpegp/idct.c b/apps/plugins/imageviewer/jpegp/idct.c index 7db1658546..659d4650e6 100644 --- a/apps/plugins/imageviewer/jpegp/idct.c +++ b/apps/plugins/imageviewer/jpegp/idct.c @@ -129,6 +129,9 @@ extern void idct_sq(short *coef, int *sq) for (i=0; i<8; i++) idct1(R+i*8, C+i); for (i=0; i<8; i++) idct1(C+i*8, R+i); - for (i=0; i<64; i++) coef[i] = CLIP[ R[i] >> 15 ]; + for (i=0; i<64; i++) { // clamp: corrupt data can exceed the CLIP table + int v= R[i] >> 15; + coef[i] = CLIP[ v < -256 ? -256 : v > 511 ? 511 : v ]; + } } diff --git a/apps/plugins/imageviewer/jpegp/jpeg81.c b/apps/plugins/imageviewer/jpegp/jpeg81.c index 83f4e287fa..05addec626 100644 --- a/apps/plugins/imageviewer/jpegp/jpeg81.c +++ b/apps/plugins/imageviewer/jpegp/jpeg81.c @@ -43,6 +43,7 @@ jpeg81.c #include "GETC.h" #include "rb_glue.h" #include "jpeg81.h" +#include "jpeg_common.h" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" @@ -147,8 +148,11 @@ static int ReadDiff(struct JPEGD *j, int s) // JPEG magnitude stuff. One way to static int ReadHuffmanCode(struct JPEGD *j, int *pb) // index into the sym-table { - int v= GetBit(j); - while ( v >= *pb ) v= 2*v + GetBit(j) - *pb++; + int v= GetBit(j), n= 16; + while ( v >= *pb ) { + if (!--n) return 0; // no code of up to 16 bits: corrupt data + v= 2*v + GetBit(j) - *pb++; + } return v; } @@ -179,6 +183,7 @@ static void ac_decode_huff(struct JPEGD *j, struct COMP *sc, TCOEF *coef) }//else ZRL } k+=r; + if (k > j->Se) return; // corrupt data coef[k]= s; if (k==j->Se) return; } @@ -206,7 +211,8 @@ static void ac_succ_huff(struct JPEGD *j, struct COMP *sc, TCOEF *coef) break; }//else ZRL } - for (; ;k++) if (!ac_refine(j, coef+k)) if (!r--) break; + for (; k <= j->Se; k++) if (!ac_refine(j, coef+k)) if (!r--) break; + if (k > j->Se) return; // corrupt data coef[k]= s; if (k==j->Se) return; } @@ -221,6 +227,7 @@ static void du_sequential_huff(struct JPEGD *j, struct COMP *sc, TCOEF *coef) dc_decode_huff(j, sc, coef); for (k=1; (s=sc->ACS[ReadHuffmanCode(j, sc->ACB)]); k++) { // EOB? k+= s>>4; + if (k > 63) return; // corrupt data if (s==0xf0) continue; // ZRL coef[k]= ReadDiff(j, s&15); if (k==63) return; @@ -634,6 +641,8 @@ static int set_dim(struct JPEGD *j, int d) // d= 1 (LL) or 8 (DCT) C->Hi= C->Vi>>4; C->Vi&= 15; C->Qi= GETC(); + if (C->Hi < 1 || C->Hi > 4 || C->Vi < 1 || C->Vi > 4 || C->Qi > 3) + return -1; // corrupt if ( C->Hi > j->Hmax ) j->Hmax = C->Hi; if ( C->Vi > j->Vmax ) j->Vmax = C->Vi; @@ -708,12 +717,13 @@ extern enum JPEGENUM JPEGDecode(struct JPEGD *j) int La= GETWbi(); printf("DAC\n"); printf(" Arithmetic Conditioning\n parameters:\n"); - for (La-=2; La; La-=2) + for (La-=2; La > 1; La-=2) { int CB= GETC(); int Tc= CB>>4; int Tb= CB&15; int Cs= GETC(); + if (Tb > 3) return JPEGENUMERR_CORRUPT; if (Tc) // AC { printf(" AC%d Kx=%d\n", Tb, Cs); @@ -743,6 +753,7 @@ extern enum JPEGENUM JPEGDecode(struct JPEGD *j) int CH= GETC(); int Tc= CH>>4; int Th= CH&15; + if (Tc > 1 || Th > 3) return JPEGENUMERR_CORRUPT; int *B= j->HTB[Tc][Th]; unsigned char *S= j->HTS[Tc][Th]; printf(" %s%d\n", Tc?"AC":"DC", Th); @@ -754,6 +765,7 @@ extern enum JPEGENUM JPEGDecode(struct JPEGD *j) B[i]= N; // running total } printf("\n"); + if (N > 256 || 17 + N > Lh) return JPEGENUMERR_CORRUPT; printf(" S: %d symbol bytes\n", N); for (i=0; iNf>4) return JPEGENUMERR_COMP4; + if (!j->Nf) return JPEGENUMERR_CORRUPT; if (!j->Y) return JPEGENUMERR_ZEROY; // I have no idea about this DNL stuff + if (!j->X) return JPEGENUMERR_CORRUPT; j->SOF= marker; if ( (j->SOF&3)==3 ) // LOSSLESS-mode { int TotalDU= set_dim(j, 1); // for malloc: in samples as coeff; + if (TotalDU < 0) return JPEGENUMERR_CORRUPT; + if (TotalDU > 0x7fffffff / (int)sizeof(DU)) return JPEGENUMERR_MALLOC; if (j->SOF > 0xC8) { // arithmetic: @@ -818,6 +834,8 @@ extern enum JPEGENUM JPEGDecode(struct JPEGD *j) else // DCT-mode { int TotalDU= set_dim(j, 8); // for malloc in DU; + if (TotalDU < 0) return JPEGENUMERR_CORRUPT; + if (TotalDU > 0x7fffffff / (int)sizeof(DU)) return JPEGENUMERR_MALLOC; printf(" %d MCU (%d x %d)\n", j->mcu_total, j->mcu_width, j->mcu_height); @@ -856,6 +874,7 @@ extern enum JPEGENUM JPEGDecode(struct JPEGD *j) else if ( marker == 0xD9 ) // EOI { printf("EOI\n"); + if (!j->scans) return JPEGENUMERR_CORRUPT; // no image data return JPEGENUM_OK; } else if ( marker == 0xDA ) // SOS @@ -864,6 +883,8 @@ extern enum JPEGENUM JPEGDecode(struct JPEGD *j) GETWbi(); //Ls printf("SOS\n"); j->Ns= GETC();//Ns + j->scans++; + if (j->Ns < 1 || j->Ns > j->Nf) return JPEGENUMERR_CORRUPT; printf(" Ns: %d (%s scan)\n", j->Ns, (j->Ns>1)?"Interleaved":"Single"); for (ci=0; ciNs; ci++) @@ -873,6 +894,7 @@ extern enum JPEGENUM JPEGDecode(struct JPEGD *j) int T= GETC(); int Td= T>>4; int Ta= T&15; + if (Td > 3 || Ta > 3) return JPEGENUMERR_CORRUPT; printf(" Cs=%d Td=%d Ta=%d\n", Cs, Td, Ta); {// safe search @@ -907,6 +929,8 @@ extern enum JPEGENUM JPEGDecode(struct JPEGD *j) j->Ah= j->Al>>4; j->Al&= 15; j->Al2= 1<Al;//pre-computed + if (j->Se > 63 || j->Ss > j->Se || j->Al > 13) + return JPEGENUMERR_CORRUPT; printf(" %s: %d\n", ((j->SOF&3)==3)?"Px":"Ss", j->Ss); printf(" Se: %d\n", j->Se); @@ -982,8 +1006,15 @@ extern enum JPEGENUM JPEGDecode(struct JPEGD *j) } else if ( (marker & 0xf0) == 0xE0 ) // APPn E0..EF { - int La= GETWbi(); - SEEK(La-2); + int La= GETWbi()-2; + int n= 0; + if ( marker == 0xE0 || marker == 0xEE ) // JFIF, Adobe: colour space + { + unsigned char id[12]; + for ( ; n < La && n < 12; n++ ) id[n]= GETC(); + jpeg_app_colorspace(marker, id, n, &j->jfif, &j->adobe); + } + SEEK(La-n); printf("APP%d\n", marker&15); } else if ( marker == 0xFE ) // COM diff --git a/apps/plugins/imageviewer/jpegp/jpeg81.h b/apps/plugins/imageviewer/jpegp/jpeg81.h index e265d84926..a5b23abf3a 100644 --- a/apps/plugins/imageviewer/jpegp/jpeg81.h +++ b/apps/plugins/imageviewer/jpegp/jpeg81.h @@ -32,8 +32,11 @@ enum JPEGENUM { JPEGENUMERR_MARKERDNL, // DNL marker found (not supported) JPEGENUMERR_ZEROY, // Y in SOFn is zero (DNL?) JPEGENUMERR_COMPNOTFOUND, // Scan component selector (Csj) not found among Component identifiers (Ci) + JPEGENUMERR_CORRUPT, // a header value out of range }; +#include + typedef short TCOEF; // 16-bit coefficients typedef TCOEF DU[64]; // The DATA UNIT typedef unsigned short TSAMP; // Lossless 'coefficients' are unsigned @@ -103,6 +106,9 @@ struct JPEGD { // The JPEG DECODER OBJECT void *jpeg_mem; // <-- free me int Hmax, Vmax; // for conversion + int scans; // scans decoded: none means no image data + bool jfif; // saw a JFIF APP0 marker + unsigned char adobe; // Adobe APP14 transform flag + 1, 0 if none int mcu_width; int mcu_height; int mcu_total; // covers the whole image diff --git a/apps/plugins/imageviewer/jpegp/jpegp.c b/apps/plugins/imageviewer/jpegp/jpegp.c index a5883e3a28..955399b2f6 100644 --- a/apps/plugins/imageviewer/jpegp/jpegp.c +++ b/apps/plugins/imageviewer/jpegp/jpegp.c @@ -4,6 +4,7 @@ #include "rb_glue.h" #include "../imageviewer.h" +#include "jpeg_common.h" /**************** begin Application ********************/ @@ -217,6 +218,8 @@ static int get_image(struct image_info *info, int frame, int ds) int v1 = j->Vmax / j->Components[1].Vi; int h2 = j->Hmax / j->Components[2].Hi; int v2 = j->Vmax / j->Components[2].Vi; + bool rgb = jpeg_is_rgb(j->jfif, j->adobe, j->Components[0].Ci, + j->Components[1].Ci, j->Components[2].Ci); int x, y; int max_y = info->height * ds; @@ -264,6 +267,9 @@ static int get_image(struct image_info *info, int frame, int ds) c2 = (sumV + area/2) / area; } + if (rgb) // components are R, G, B: no conversion + *bmp++= FB_RGBPACK(c0, c1, c2); + else // ITU BT.601 full-range YUV-to-RGB integer approximation { int y = (c0 << 5) + 16; diff --git a/apps/plugins/imageviewer/png/png.c b/apps/plugins/imageviewer/png/png.c index 5f9aa9393c..5fb999d060 100644 --- a/apps/plugins/imageviewer/png/png.c +++ b/apps/plugins/imageviewer/png/png.c @@ -120,7 +120,7 @@ static int load_image(char *filename, struct image_info *info, } else { - file_size = rb->filesize(fd); + file_size = rb->ffilesize(fd); } DEBUGF("reading file '%s'\n", filename); diff --git a/apps/plugins/imageviewer/ppm/ppm.c b/apps/plugins/imageviewer/ppm/ppm.c index f2bdc07d68..e1ae1b278d 100644 --- a/apps/plugins/imageviewer/ppm/ppm.c +++ b/apps/plugins/imageviewer/ppm/ppm.c @@ -106,7 +106,7 @@ static int load_image(char *filename, struct image_info *info, } else { - filesize = rb->filesize(fd); + filesize = rb->ffilesize(fd); } DEBUGF("reading file '%s'\n", filename); diff --git a/apps/plugins/invadrox.c b/apps/plugins/invadrox.c index f365e04534..7553ba8537 100644 --- a/apps/plugins/invadrox.c +++ b/apps/plugins/invadrox.c @@ -280,7 +280,7 @@ CONFIG_KEYPAD == MROBE500_PAD #define RIGHT BUTTON_RIGHT #define FIRE BUTTON_SELECT -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD /* use touchscreen */ #elif CONFIG_KEYPAD == MA_PAD @@ -297,13 +297,20 @@ CONFIG_KEYPAD == MROBE500_PAD #define RIGHT BUTTON_RIGHT #define FIRE BUTTON_A -#elif CONFIG_KEYPAD == CTRU_PAD +#elif CONFIG_KEYPAD == N3DS_PAD #define QUIT BUTTON_POWER #define LEFT BUTTON_LEFT #define RIGHT BUTTON_RIGHT #define FIRE BUTTON_SELECT +#elif CONFIG_KEYPAD == ECHO_R1_PAD + +#define QUIT BUTTON_POWER +#define LEFT BUTTON_LEFT +#define RIGHT BUTTON_RIGHT +#define FIRE BUTTON_A + #else #error INVADROX: Unsupported keypad #endif @@ -388,8 +395,8 @@ CONFIG_KEYPAD == MROBE500_PAD #define LIVES_X 10 #define MAX_Y 18 -/* iPod Video defines */ -#elif (LCD_WIDTH == 320) && (LCD_HEIGHT == 240) +/* iPod Video defines - and Samsung YP-CP3, 400x240: the same, centred */ +#elif ((LCD_WIDTH == 320) || (LCD_WIDTH == 400)) && (LCD_HEIGHT == 240) /* Original arcade game size 224x240, 1bpp with * red overlay at top and green overlay at bottom. @@ -420,11 +427,11 @@ CONFIG_KEYPAD == MROBE500_PAD */ #define ARCADISH_GRAPHICS -#define PLAYFIELD_X 48 +#define PLAYFIELD_X (48 + (LCD_WIDTH - 320) / 2) #define SHIP_Y (PLAYFIELD_Y - 3 * SHIP_HEIGHT) #define ALIEN_START_Y (UFO_Y + 3 * ALIEN_HEIGHT) #define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH) -#define SCORENUM_Y SCORE_Y + (2 * (FONT_HEIGHT + 1) + 1) +#define SCORENUM_Y (SCORE_Y + 2 * (FONT_HEIGHT + 1) + 1) #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING) #define SHIELD_Y (PLAYFIELD_Y - 6 * SHIP_HEIGHT) #define LIVES_X 10 @@ -448,7 +455,7 @@ CONFIG_KEYPAD == MROBE500_PAD #define SHIP_Y (PLAYFIELD_Y - 3 * SHIP_HEIGHT) #define ALIEN_START_Y (UFO_Y + 3 * ALIEN_HEIGHT) #define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH) + 10 -#define SCORENUM_Y SCORE_Y + (2 * (FONT_HEIGHT + 1) + 1) +#define SCORENUM_Y (SCORE_Y + 2 * (FONT_HEIGHT + 1) + 1) #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 8 * NUM_SPACING) #define SHIELD_Y (PLAYFIELD_Y - 6 * SHIP_HEIGHT) #define LIVES_X 10 @@ -638,7 +645,7 @@ CONFIG_KEYPAD == MROBE500_PAD #undef SCORE_Y #define SCORE_Y 80 #define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH) -#define SCORENUM_Y SCORE_Y + (2 * (FONT_HEIGHT + 1) + 1) +#define SCORENUM_Y (SCORE_Y + 2 * (FONT_HEIGHT + 1) + 1) #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING) #define SHIELD_Y (PLAYFIELD_Y - 6 * SHIP_HEIGHT) #define LIVES_X 10 @@ -680,6 +687,24 @@ CONFIG_KEYPAD == MROBE500_PAD #define LIVES_X 8 #define MAX_Y 15 +#elif (LCD_WIDTH == 360) && (LCD_HEIGHT == 640) + +/* Hidizs AP80 Pro Max + */ +#define ARCADISH_GRAPHICS +#define PLAYFIELD_X 32 +#define SHIP_Y (PLAYFIELD_Y - 3 * SHIP_HEIGHT) +#define ALIEN_START_Y (UFO_Y + 3 * ALIEN_HEIGHT) +/* Redefine SCORE_Y */ +#undef SCORE_Y +#define SCORE_Y 80 +#define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH) +#define SCORENUM_Y (SCORE_Y + 2 * (FONT_HEIGHT + 1) + 1) +#define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING) +#define SHIELD_Y (PLAYFIELD_Y - 6 * SHIP_HEIGHT) +#define LIVES_X 10 +#define MAX_Y 30 + #elif (LCD_WIDTH == 360) && (LCD_HEIGHT == 400) /* Shanling Q1 @@ -692,7 +717,7 @@ CONFIG_KEYPAD == MROBE500_PAD #undef SCORE_Y #define SCORE_Y 80 #define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH) -#define SCORENUM_Y SCORE_Y + (2 * (FONT_HEIGHT + 1) + 1) +#define SCORENUM_Y (SCORE_Y + 2 * (FONT_HEIGHT + 1) + 1) #define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING) #define SHIELD_Y (PLAYFIELD_Y - 6 * SHIP_HEIGHT) #define LIVES_X 10 @@ -1608,8 +1633,15 @@ static void move_ufo(void) static void draw_background(void) { - +#if BMPWIDTH_invadrox_background < LCD_WIDTH + /* a narrower background, centred like the playfield */ + rb->lcd_clear_display(); + rb->lcd_bitmap(invadrox_background, + (LCD_WIDTH - BMPWIDTH_invadrox_background) / 2, 0, + BMPWIDTH_invadrox_background, LCD_HEIGHT); +#else rb->lcd_bitmap(invadrox_background, 0, 0, LCD_WIDTH, LCD_HEIGHT); +#endif rb->lcd_update(); } diff --git a/apps/plugins/iriver_flash.c b/apps/plugins/iriver_flash.c index a67b793607..2b6f70adf9 100644 --- a/apps/plugins/iriver_flash.c +++ b/apps/plugins/iriver_flash.c @@ -23,6 +23,7 @@ #include "plugin.h" #include "lib/helper.h" #include "checksum.h" +#include "system-iriver.h" /* * Flash commands may rely on null pointer dereferences to work correctly. @@ -373,7 +374,7 @@ static bool load_firmware(const char* filename, enum firmware firmware, } /* get file and buffer lengths and acquire the buffer */ - fd_len = rb->filesize(fd); + fd_len = rb->ffilesize(fd); buffer = rb->plugin_get_audio_buffer(&buffer_len); /* ensure there's enough space in the buffer */ diff --git a/apps/plugins/jewels.c b/apps/plugins/jewels.c index 0cf4fd1743..02953847b1 100644 --- a/apps/plugins/jewels.c +++ b/apps/plugins/jewels.c @@ -366,7 +366,7 @@ CONFIG_KEYPAD == MROBE500_PAD #define HK_SELECT "SELECT" #define HK_CANCEL "BACK" -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD /* use touchscreen */ #elif CONFIG_KEYPAD == MA_PAD @@ -389,7 +389,7 @@ CONFIG_KEYPAD == MROBE500_PAD #define HK_SELECT "A" #define HK_CANCEL "START" -#elif CONFIG_KEYPAD == CTRU_PAD +#elif CONFIG_KEYPAD == N3DS_PAD #define JEWELS_UP BUTTON_UP #define JEWELS_DOWN BUTTON_DOWN #define JEWELS_LEFT BUTTON_LEFT @@ -399,6 +399,16 @@ CONFIG_KEYPAD == MROBE500_PAD #define HK_SELECT "A" #define HK_CANCEL "B" +#elif CONFIG_KEYPAD == ECHO_R1_PAD +#define JEWELS_UP BUTTON_UP +#define JEWELS_DOWN BUTTON_DOWN +#define JEWELS_LEFT BUTTON_LEFT +#define JEWELS_RIGHT BUTTON_RIGHT +#define JEWELS_SELECT BUTTON_A +#define JEWELS_CANCEL BUTTON_POWER +#define HK_SELECT "A" +#define HK_CANCEL "POWER" + #else #error No keymap defined! #endif diff --git a/apps/plugins/keyremap.c b/apps/plugins/keyremap.c index 9917bc567e..04ef074ff6 100644 --- a/apps/plugins/keyremap.c +++ b/apps/plugins/keyremap.c @@ -374,7 +374,7 @@ static int keyremap_open_file(const char *filename, int *fd, size_t *fsize) *fd = rb->open(filename, O_RDONLY); if (*fd) { - *fsize = rb->filesize(*fd); + *fsize = rb->ffilesize(*fd); count = *fsize / sizeof(struct button_mapping); @@ -746,11 +746,14 @@ static int keymap_add_button_entry(int context, int action_code, for (int i = 0; i < ctx_data.act_count; i++) { if (ctx_data.act_map[i].context == context && - ctx_data.act_map[i].map.action_code == action_code) + ctx_data.act_map[i].map.action_code == action_code && + ctx_data.act_map[i].map.button_code == button_code && + ctx_data.act_map[i].map.pre_button_code == pre_button_code) { - /*Duplicate -- Update the existing entry */ - ctx_data.act_map[i].map.button_code = button_code; - ctx_data.act_map[i].map.pre_button_code = pre_button_code; + /*Duplicate -- Use the existing entry */ + /* FS#13975 allow multiple entries to map same action code */ + logf("keyremap: skipping duplicate @ [%d] ctx: %d, act: %d btn: %d pbtn: %d", + i, context, action_code, button_code, pre_button_code); return ctx_data.act_count; } } diff --git a/apps/plugins/lastfm_scrobbler.c b/apps/plugins/lastfm_scrobbler.c index 93b520bee3..f175b07fab 100644 --- a/apps/plugins/lastfm_scrobbler.c +++ b/apps/plugins/lastfm_scrobbler.c @@ -66,7 +66,6 @@ Example All fields except those marked (optional) above are required. */ -#include "rbversion.h" #include "plugin.h" #include "lib/configfile.h" @@ -87,8 +86,6 @@ Example /* increment this on any code change that effects output */ #define SCROBBLER_VERSION "1.1" -#define SCROBBLER_REVISION " " RBVERSION " " - #define SCROBBLER_BAD_ENTRY "# FAILED - " /* longest entry I've had is 323, add a safety margin */ @@ -531,8 +528,8 @@ static int sbl_check_or_open(bool check_only) /* write file header */ rb->fdprintf(fd, "#AUDIOSCROBBLER/" SCROBBLER_VERSION "\n" "#TZ/UNKNOWN\n" "#CLIENT/Rockbox " - TARGET_NAME SCROBBLER_REVISION - HDR_STR_TIMELESS "\n"); + TARGET_NAME " %s " + HDR_STR_TIMELESS "\n", rb->rbversion); rb->fdprintf(fd, ITEM_HDR); } else diff --git a/apps/plugins/lastfm_scrobbler_viewer.c b/apps/plugins/lastfm_scrobbler_viewer.c index 8ce5ab8b85..c138d4976e 100644 --- a/apps/plugins/lastfm_scrobbler_viewer.c +++ b/apps/plugins/lastfm_scrobbler_viewer.c @@ -200,7 +200,7 @@ static size_t file_open(const char *filename, int *fd) *fd = rb->open(filename, O_RDONLY); if (*fd >= 0) { - fsize = rb->filesize(*fd); + fsize = rb->ffilesize(*fd); } } return fsize; diff --git a/apps/plugins/lib/helper.c b/apps/plugins/lib/helper.c index 8c5bc06e8a..393df469a2 100644 --- a/apps/plugins/lib/helper.c +++ b/apps/plugins/lib/helper.c @@ -56,6 +56,7 @@ void backlight_use_settings(void) rb->backlight_set_timeout_plugged(rb->global_settings-> backlight_timeout_plugged); #endif /* CONFIG_CHARGING */ + rb->backlight_set_on_button_hold(rb->global_settings->backlight_on_button_hold); } #else /* HAVE_BACKLIGHT */ /* DUMMY FUNCTIONS */ @@ -113,6 +114,9 @@ void remote_backlight_use_settings(void) rb->remote_backlight_set_timeout_plugged(rb->global_settings-> remote_backlight_timeout_plugged); #endif /* CONFIG_CHARGING */ +#ifdef HAS_REMOTE_BUTTON_HOLD + rb->remote_backlight_set_on_button_hold(rb->global_settings->remote_backlight_on_button_hold); +#endif } #else /* HAVE_REMOTE_LCD */ /* DUMMY FUNCTIONS */ diff --git a/apps/plugins/lib/keymaps.h b/apps/plugins/lib/keymaps.h index 3cd281bf2e..b67a7aeabe 100644 --- a/apps/plugins/lib/keymaps.h +++ b/apps/plugins/lib/keymaps.h @@ -249,7 +249,7 @@ #define BTN_FIRE BUTTON_SELECT #define BTN_PAUSE BUTTON_POWER -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD #define BTN_FIRE BUTTON_CENTER #define BTN_PAUSE BUTTON_POWER #define BTN_HAVE_DIAGONAL @@ -274,7 +274,7 @@ #define BTN_FIRE BUTTON_A #define BTN_PAUSE BUTTON_START -#elif CONFIG_KEYPAD == CTRU_PAD +#elif CONFIG_KEYPAD == N3DS_PAD #define BTN_UP BUTTON_UP #define BTN_DOWN BUTTON_DOWN #define BTN_LEFT BUTTON_LEFT @@ -282,6 +282,14 @@ #define BTN_FIRE BUTTON_USER #define BTN_PAUSE BUTTON_MENU +#elif CONFIG_KEYPAD == ECHO_R1_PAD +#define BTN_UP BUTTON_UP +#define BTN_DOWN BUTTON_DOWN +#define BTN_LEFT BUTTON_LEFT +#define BTN_RIGHT BUTTON_RIGHT +#define BTN_FIRE BUTTON_A +#define BTN_PAUSE BUTTON_POWER + #else #error Unsupported keypad #endif @@ -310,7 +318,8 @@ (CONFIG_KEYPAD != CREATIVE_ZENXFI2_PAD) && \ (CONFIG_KEYPAD != SHANLING_Q1_PAD) && \ (CONFIG_KEYPAD != HIBY_R3PROII_PAD) && \ - (CONFIG_KEYPAD != CTRU_PAD) + (CONFIG_KEYPAD != HIDIZS_AP80MAX_PAD) && \ + (CONFIG_KEYPAD != N3DS_PAD) #define BTN_FIRE BUTTON_BOTTOMLEFT #define BTN_PAUSE BUTTON_TOPLEFT #endif diff --git a/apps/plugins/lib/pluginlib_actions.c b/apps/plugins/lib/pluginlib_actions.c index 5d2c0e323a..6dadfbc784 100644 --- a/apps/plugins/lib/pluginlib_actions.c +++ b/apps/plugins/lib/pluginlib_actions.c @@ -284,7 +284,16 @@ const struct button_mapping pla_main_ctx[] = { PLA_DOWN_REPEAT, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE }, { PLA_LEFT_REPEAT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE }, { PLA_RIGHT_REPEAT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE }, -#elif (CONFIG_KEYPAD == CTRU_PAD) +#elif (CONFIG_KEYPAD == N3DS_PAD) + { PLA_UP, BUTTON_UP, BUTTON_NONE }, + { PLA_DOWN, BUTTON_DOWN, BUTTON_NONE }, + { PLA_LEFT, BUTTON_LEFT, BUTTON_NONE }, + { PLA_RIGHT, BUTTON_RIGHT, BUTTON_NONE }, + { PLA_UP_REPEAT, BUTTON_UP|BUTTON_REPEAT, BUTTON_NONE }, + { PLA_DOWN_REPEAT, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE }, + { PLA_LEFT_REPEAT, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE }, + { PLA_RIGHT_REPEAT, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE }, +#elif CONFIG_KEYPAD == ECHO_R1_PAD { PLA_UP, BUTTON_UP, BUTTON_NONE }, { PLA_DOWN, BUTTON_DOWN, BUTTON_NONE }, { PLA_LEFT, BUTTON_LEFT, BUTTON_NONE }, @@ -520,7 +529,7 @@ const struct button_mapping pla_main_ctx[] = {PLA_SELECT, BUTTON_SELECT, BUTTON_NONE}, {PLA_SELECT_REL, BUTTON_SELECT|BUTTON_REL, BUTTON_SELECT}, {PLA_SELECT_REPEAT, BUTTON_SELECT|BUTTON_REPEAT, BUTTON_NONE}, -#elif (CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD) +#elif (CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD) {PLA_EXIT, BUTTON_POWER, BUTTON_NONE}, #elif (CONFIG_KEYPAD == MA_PAD) {PLA_CANCEL, BUTTON_BACK|BUTTON_REL, BUTTON_BACK}, @@ -534,12 +543,18 @@ const struct button_mapping pla_main_ctx[] = {PLA_SELECT, BUTTON_A, BUTTON_NONE}, {PLA_SELECT_REL, BUTTON_A|BUTTON_REL, BUTTON_A}, {PLA_SELECT_REPEAT, BUTTON_A|BUTTON_REPEAT, BUTTON_NONE}, -#elif (CONFIG_KEYPAD == CTRU_PAD) +#elif (CONFIG_KEYPAD == N3DS_PAD) {PLA_CANCEL, BUTTON_BACK, BUTTON_NONE}, {PLA_EXIT, BUTTON_MENU, BUTTON_NONE}, {PLA_SELECT, BUTTON_SELECT, BUTTON_NONE}, {PLA_SELECT_REL, BUTTON_SELECT|BUTTON_REL, BUTTON_SELECT}, {PLA_SELECT_REPEAT, BUTTON_SELECT|BUTTON_REPEAT, BUTTON_NONE}, +#elif (CONFIG_KEYPAD == ECHO_R1_PAD) + {PLA_CANCEL, BUTTON_B, BUTTON_NONE}, + {PLA_EXIT, BUTTON_POWER, BUTTON_NONE}, + {PLA_SELECT, BUTTON_A, BUTTON_NONE}, + {PLA_SELECT_REL, BUTTON_A|BUTTON_REL, BUTTON_SELECT}, + {PLA_SELECT_REPEAT, BUTTON_A|BUTTON_REPEAT, BUTTON_NONE}, #else # ifndef HAVE_TOUCHSCREEN # error pluginlib_actions: No actions defined diff --git a/apps/plugins/lib/pluginlib_jpeg_load.c b/apps/plugins/lib/pluginlib_jpeg_load.c index e88f890d0a..bc734f80d9 100644 --- a/apps/plugins/lib/pluginlib_jpeg_load.c +++ b/apps/plugins/lib/pluginlib_jpeg_load.c @@ -23,5 +23,5 @@ #include #include "wrappers.h" -#define filesize rb->filesize +#define ffilesize rb->ffilesize #include "../../recorder/jpeg_load.c" diff --git a/apps/plugins/lrcplayer.c b/apps/plugins/lrcplayer.c index 8ce00fad7a..7468e24f08 100644 --- a/apps/plugins/lrcplayer.c +++ b/apps/plugins/lrcplayer.c @@ -1944,7 +1944,7 @@ static void save_changes(void) rb->write(fd, BOM, BOM_SIZE); } else - size = rb->filesize(fe); + size = rb->ffilesize(fe); while (curr < size) { /* find offset of next tag */ diff --git a/apps/plugins/lua/include_lua/filebrowse.lua b/apps/plugins/lua/include_lua/filebrowse.lua index fb74798ccb..d155074e29 100755 --- a/apps/plugins/lua/include_lua/filebrowse.lua +++ b/apps/plugins/lua/include_lua/filebrowse.lua @@ -29,9 +29,10 @@ local tmploader = require("temploader") -------------------------------------------------------------------------------- -- uses print_table and get_files to display simple file browser --- sort_by "date" "name" "size" +-- sort_by "date" "name" "size" "type" -- descending true/false -function file_choose(dir, title, sort_by, descending) +-- returns selected file and attribs string if get_attr == true +function file_choose(dir, title, sort_by, descending, get_attr) local dstr, hstr = "" if not title then dstr = "%d items found in %0d.%02d seconds" @@ -58,6 +59,7 @@ function file_choose(dir, title, sort_by, descending) local timer local files = {} local dirs = {} + local attrs = {} local item = 1 local cancel_fn = nil @@ -82,20 +84,37 @@ function file_choose(dir, title, sort_by, descending) rb.splash(1, "Searching for Files") dirs, files = get_files(dir, recurse, f_finddir, f_findfile, sort_by, cancel_fn, dirs, files) + local ndirs = #dirs local parentdir = dirs[1] - for i = 1, #dirs do + for i = 1, ndirs do dirs[i] = "\t" .. dirs[i] end - if not descending then - for i = 1, #files do - -- only store file name .. strip attributes from end - table.insert(dirs, "\t" .. string.match(files[i], "[^;]+") or "?") + if not get_attr then + if not descending then + for i = 1, #files do + -- only store file name .. strip attributes from end and parent from beginning + table.insert(dirs, "\t" .. string.match(string.match(files[i], "[^/\\]+$") or "?", "[^;]+") or "?") + end + else + for i = #files, 1, -1 do + -- only store file name .. strip attributes from end and parent from beginning + table.insert(dirs, "\t" .. string.match(string.match(files[i], "[^/\\]+$") or "?", "[^;]+") or "?") + end end - else - for i = #files, 1, -1 do - -- only store file name .. strip attributes from end - table.insert(dirs, "\t" .. string.match(files[i], "[^;]+") or "?") + else -- get_attr == true + if not descending then + for i = 1, #files do + -- only store file name .. strip attributes from end and parent from beginning + table.insert(dirs, "\t" .. string.match(string.match(files[i], "[^/\\]+$") or "?", "[^;]+") or "?") + table.insert(attrs, string.match(files[i], ";(.+)")) + end + else + for i = #files, 1, -1 do + -- only store file name .. strip attributes from end and parent from beginning + table.insert(dirs, "\t" .. string.match(string.match(files[i], "[^/\\]+$") or "?", "[^;]+") or "?") + table.insert(attrs, string.match(files[i], ";(.+)")) + end end end for i=1, #files do files[i] = nil end -- empty table for reuse @@ -112,7 +131,11 @@ function file_choose(dir, title, sort_by, descending) if item > 0 then dir = string.gsub(dirs[item], "%c+","") if not rb.dir_exists("/" .. dir) then - return dir + if (parentdir == "/") then + return parentdir .. dir, attrs[item - ndirs - 1] -- -1 for hstr + else + return parentdir .. "/" .. dir, attrs[item - ndirs - 1] + end end end @@ -121,7 +144,7 @@ function file_choose(dir, title, sort_by, descending) if dir == "" then dir = "/" end end for i=1, #dirs do dirs[i] = nil end -- empty table for reuse - + for i=1, #attrs do attrs[i] = nil end -- empty table for reuse end end -- file_choose -------------------------------------------------------------------------------- diff --git a/apps/plugins/lua/include_lua/get_files.lua b/apps/plugins/lua/include_lua/get_files.lua index f9e7a66699..01a98d3ed7 100644 --- a/apps/plugins/lua/include_lua/get_files.lua +++ b/apps/plugins/lua/include_lua/get_files.lua @@ -27,9 +27,9 @@ if ... == nil then rb.splash(rb.HZ * 3, "use 'require'") end -- findfile & finddir are definable search functions -- if not defined all files/dirs are returned if false is passed.. none or you can provide your own function see below.. --- sort_by can be by "name" "size" "date" or "none" to perform no sorting +-- sort_by can be by "name" "size" "date" "type" or "none" to perform no sorting note: for "size" and "date" you may need to strip the attribute data to use - the returned filename + the returned filename e.g. string.match(file, "[^;]+") -- cancel_fn if not defined or not a function no user cancel otherwise supply your own cancel function which returns true to cancel searching for files -- f_t and d_t allow you to pass your own tables for re-use but isn't necessary @@ -109,7 +109,7 @@ local function get_files(path, recurse, finddir, findfile, sort_by, cancel_fn, f if sort_by == "name" then sort_by_function = function(s1, s2) return s1 < s2 end filepath_function = function(path, sep, fname, fattrib, fsize, ftime) - return string.format("%s%s%s;", path, sep, fname) + return string.format("%s%s%s", path, sep, fname) end elseif sort_by == "size" then filepath_function = function(path, sep, fname, fattrib, fsize, ftime) @@ -137,9 +137,26 @@ local function get_files(path, recurse, finddir, findfile, sort_by, cancel_fn, f end return s1 < s2 end + elseif sort_by == "type" then + filepath_function = function(path, sep, fname, fattrib, fsize, ftime) + return string.format("%s%s%s; At:%d, Sz:%d, Tm:%d", path, sep, fname, fattrib, fsize, ftime) + end + sort_by_function = function(s1, s2) + local v1, v2 + v1 = string.match(s1, ".+%.([^%s]+).AT:") + v2 = string.match(s2, ".+%.([^%s]+).AT:") + + if v1 or v2 then + --rb.splash(20, (v1 or "?") .. " " .. (v2 or "?")) + if (v1 ~= v2) then + return (v1 or "") < (v2 or "") + end + end + return s1 < s2 + end else -- "none" filepath_function = function(path, sep, fname, fattrib, fsize, ftime) - return string.format("%s%s%s;", path, sep, fname) + return string.format("%s%s%s", path, sep, fname) end end @@ -147,7 +164,7 @@ local function get_files(path, recurse, finddir, findfile, sort_by, cancel_fn, f for key,value in pairs(dirs) do --luadir.dir may error out so we need to do the call protected - -- _get_files(value, CANCEL_BUTTON) + -- _get_files(value) _, quit = pcall(_get_files, value) if quit == true or not recurse then diff --git a/apps/plugins/lua/include_lua/rbsettings.lua b/apps/plugins/lua/include_lua/rbsettings.lua index 4da6ee61b2..2aaf06500f 100644 --- a/apps/plugins/lua/include_lua/rbsettings.lua +++ b/apps/plugins/lua/include_lua/rbsettings.lua @@ -1,4 +1,4 @@ ---[[ Lua rb settings reader +--[[ Lua rb settings reader / writer /*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ @@ -7,7 +7,7 @@ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * - * Copyright (C) 2019 William Wilgus + * Copyright (C) 2026 William Wilgus * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -18,6 +18,33 @@ * KIND, either express or implied. * ****************************************************************************/ +see https://www.rockbox.org/forums/index.php/topic,55820.0.html for an example +see rocklib.c for definitive list of supported settings +rb.global_settings() +rb.global_status() +rb.audio_next_track() +rb.audio_current_track() + +NOTES: +Not all settings are accessible from lua, for instance you can read char_ptrs +but writing them is not supported since we cannot guarantee the lifetime once +lua terminates, or settigns containing unions since its not possible to discern +what the value is at compile time these items will be an opaque pointer to the +structure in memory but lua does not know how to decode it + +If you are writing settings that you want to persist do not forget to call +rb.settings_save() though not calling it does NOT guarantee a changed setting +won't be written behind your back so be sure to save the old value +and restore it when you are finished and call rb.settings_save() +to ensure the old value gets saved + +To Write stucts and arrays it is recommended to first make a call to +newval = settings_read('global_settings', 'setting.foo', 'system') +and make changes to the returned table + newval.foo = bar +followed by + setting_writesettings_read('global_settings', 'rb.foo', 'system', newval) +with the altered table. ]] rb.settings = rb.settings or {} @@ -47,6 +74,7 @@ end local function format_val(val, var_type) local ret, num + if var_type == nil then return nil elseif var_type == "str" then @@ -71,6 +99,9 @@ local function format_val(val, var_type) return ret end +--[[ ex. dump_struct(rb.global_settings, + rb.system.replaygain_settings, 0, [0x2c, 12, s_replaygain_settings])]] + local function dump_struct(t_settings, t_struct, n_elems, t_var) --Internal function dumps structs local tdata = {} @@ -101,6 +132,44 @@ local function dump_struct(t_settings, t_struct, n_elems, t_var) return tdata end +--[[ ex. write_struct(rb.global_settings, + rb.system.eq_band_setting, 10, [ 0x54, 120, s_eq_band_setting[10] ])]] +local function write_struct(t_settings, t_struct, n_elems, t_var, tdata) + --Internal function writes structs + local function struct_write_elem(v, elem_offset, newval) + local offset, tvar1 + tvar1 = get_var_fields(v) + offset = t_var[var.offset] + tvar1[var.offset] + elem_offset + return t_settings(offset, tvar1[var.size], newval) == 1 + end + + if n_elems > 0 then + -- Array of structs, struct[elems]; + local elemsize = (t_var[var.size] / n_elems) + for i = 0, n_elems - 1 do + tdata[i] = tdata[i] or {} + for k1, v1 in pairs(t_struct) do + --tdata[i][k1] = struct_get_elem(v1, (elemsize * i)) + if tdata[i][k1] ~= nil then + if not struct_write_elem(v1, (elemsize * i), tdata[i][k1]) then + return false + end + end + end + end + else + -- single struct, struct; + for k1, v1 in pairs(t_struct) do + if tdata[k1] ~= nil then + if not struct_write_elem(v1, 0, tdata[k1]) then + return false + end + end + end + end + return true +end + local function get_array_elems(var_type) --extract the number of elements, returns 0 if not found local elems = string.match(var_type,".*%[(%d+)%]") @@ -150,6 +219,57 @@ function rb.settings.read(s_settings, s_var, s_groupname) return data end +function rb.settings.write(s_settings, s_var, s_groupname, newval) + local data, val + local res = 0 + local tvar = get_var_fields(s_var) + if tvar == nil then return nil end + + local elems = get_array_elems(tvar[var.type]) + local structname = get_struct_name(tvar[var.type]) + + local tsettings = rb[s_settings] + if not tsettings then error(s_settings .. " does not exist") end + if not newval then error(s_settings .. " nothing to write") end + + if structname and rb[s_groupname] then + -- rb.splash(300, "struct " .. structname .. " - " .. s_groupname) + if type (newval) ~= "table" then + error("newval table expected got " .. type(newval)) + end + if not write_struct(tsettings, rb[s_groupname][structname], elems, tvar, newval) then + error(s_groupname .. " writing failed") + end + return 1 + end + + local voffset, vsize, vtype = tvar[var.offset], tvar[var.size], tvar[var.type] + if elems > 0 then + if type (newval) ~= "table" then + error("newval table expected got " .. type(newval)) + end + -- Arrays of values, val[elems]; + local elemsize = (vsize / elems) + + for i = 0, elems - 1 do + if newval[i] ~= nil then + if tsettings(voffset + (elemsize * i), elemsize, newval[i]) ~= 1 then + res = 0 + break + end + end + end + else + -- Single value, val; + if vtype == "ptr_char" then -- (**char) + error(s_var .. " is a string pointer") + else + res = tsettings(voffset, vsize, newval) + end + end + return res +end + function rb.settings.dump(s_settings, s_groupname, s_structname, t_output, fn_filter) t_output = t_output or {} fn_filter = fn_filter or function(s,k) return true end diff --git a/apps/plugins/lua/liolib.c b/apps/plugins/lua/liolib.c index 22213191a5..e8b3f9106f 100644 --- a/apps/plugins/lua/liolib.c +++ b/apps/plugins/lua/liolib.c @@ -267,7 +267,7 @@ static int read_number (lua_State *L, int *f) { static int test_eof (lua_State *L, int *f) { off_t s = lseek(*f, 0, SEEK_CUR); lua_pushlstring(L, NULL, 0); - return s < filesize(*f); + return s < ffilesize(*f); } diff --git a/apps/plugins/lua/lua.make b/apps/plugins/lua/lua.make index 77216b3c1f..8563290612 100644 --- a/apps/plugins/lua/lua.make +++ b/apps/plugins/lua/lua.make @@ -27,7 +27,7 @@ all: $(subst $(LUA_INCLUDEDIR)/,$(LUA_BUILDDIR)/,$(LUA_INCLUDELIST)) $(LUA_BUILDDIR)/lua.rock: $(LUA_OBJ) $(TLSFLIB) $(LUA_BUILDDIR)/actions.lua $(LUA_BUILDDIR)/buttons.lua $(LUA_BUILDDIR)/settings.lua \ $(LUA_BUILDDIR)/rocklib_aux.o $(LUA_BUILDDIR)/rb_defines.lua $(LUA_BUILDDIR)/sound_defines.lua \ - $(LUA_INCLUDELIST) + $(LUA_BUILDDIR)/audio_status.lua $(LUA_BUILDDIR)/file_attrs.lua $(LUA_INCLUDELIST) $(LUA_BUILDDIR)/actions.lua: $(LUA_OBJ) $(LUA_SRCDIR)/action_helper.pl $(call PRINTS,GEN $(@F))$(CC) $(PLUGINFLAGS) $(INCLUDES) -E -P $(APPSDIR)/plugins/lib/pluginlib_actions.h | $(LUA_SRCDIR)/action_helper.pl > $(LUA_BUILDDIR)/actions.lua @@ -52,6 +52,16 @@ $(LUA_BUILDDIR)/sound_defines.lua: $(LUA_OBJ) $(LUA_SRCDIR)/rbdefines_helper.pl $(HOSTCC) -fno-builtin $(HOST_INCLUDES) -x c -o $(LUA_BUILDDIR)/sounddefines_helper - $(call PRINTS,GEN $(@F))$(LUA_BUILDDIR)/sounddefines_helper > $(LUA_BUILDDIR)/sound_defines.lua +$(LUA_BUILDDIR)/audio_status.lua: $(LUA_OBJ) $(LUA_SRCDIR)/rbdefines_helper.pl + $(SILENT)$(CC) $(INCLUDES) -dD -E -P $(TARGET) $(CFLAGS) -include plugin.h - < /dev/null | $(LUA_SRCDIR)/rbdefines_helper.pl "audio_status" | \ + $(HOSTCC) -fno-builtin $(HOST_INCLUDES) -x c -o $(LUA_BUILDDIR)/audiodefines_helper - + $(call PRINTS,GEN $(@F))$(LUA_BUILDDIR)/audiodefines_helper > $(LUA_BUILDDIR)/audio_status.lua + +$(LUA_BUILDDIR)/file_attrs.lua: $(LUA_OBJ) $(LUA_SRCDIR)/rbdefines_helper.pl + $(SILENT)$(CC) $(INCLUDES) -dD -E -P $(TARGET) $(CFLAGS) -include plugin.h - < /dev/null | $(LUA_SRCDIR)/rbdefines_helper.pl "file_attrs" | \ + $(HOSTCC) -fno-builtin $(HOST_INCLUDES) -x c -o $(LUA_BUILDDIR)/fileattrs_helper - + $(call PRINTS,GEN $(@F))$(LUA_BUILDDIR)/fileattrs_helper > $(LUA_BUILDDIR)/file_attrs.lua + $(LUA_BUILDDIR)/rocklib_aux.c: $(APPSDIR)/plugin.h $(LUA_OBJ) $(LUA_SRCDIR)/rocklib_aux.pl $(call PRINTS,GEN $(@F))$(CC) $(PLUGINFLAGS) $(INCLUDES) -E -P -include plugin.h - < /dev/null | $(LUA_SRCDIR)/rocklib_aux.pl $(LUA_SRCDIR) > $(LUA_BUILDDIR)/rocklib_aux.c diff --git a/apps/plugins/lua/luadir.c b/apps/plugins/lua/luadir.c index cf0b4a9f8b..1f8c929239 100644 --- a/apps/plugins/lua/luadir.c +++ b/apps/plugins/lua/luadir.c @@ -31,7 +31,6 @@ #define DIR_METATABLE "directory metatable" typedef struct dir_data { - int closed; DIR *dir; } dir_data; @@ -55,7 +54,7 @@ static int dir_iter (lua_State *L) { struct dirent *entry; dir_data *d = (dir_data *)luaL_checkudata (L, 1, DIR_METATABLE); - luaL_argcheck (L, !d->closed, 1, "closed directory"); + luaL_argcheck (L, d->dir != NULL, 1, "closed directory"); if ((entry = readdir (d->dir)) != NULL) { struct dirinfo info = dir_get_info(d->dir, entry); @@ -78,7 +77,7 @@ static int dir_iter (lua_State *L) { } else { /* no more entries => close directory */ closedir (d->dir); - d->closed = 1; + d->dir = NULL; return 0; } } @@ -90,9 +89,9 @@ static int dir_iter (lua_State *L) { static int dir_close (lua_State *L) { dir_data *d = (dir_data *)lua_touserdata (L, 1); - if (!d->closed && d->dir) { + if (d->dir) { closedir (d->dir); - d->closed = 1; + d->dir = NULL; } return 0; } @@ -107,7 +106,6 @@ static int dir_iter_factory (lua_State *L) { lua_settop(L, 2); /* index 2 (bool) return attribute table */ lua_pushcclosure(L, &dir_iter, 1); d = (dir_data *) lua_newuserdata (L, sizeof(dir_data)); - d->closed = 0; luaL_getmetatable (L, DIR_METATABLE); lua_setmetatable (L, -2); diff --git a/apps/plugins/lua/rbdefines_helper.pl b/apps/plugins/lua/rbdefines_helper.pl index 8e65408d0f..639cb7d569 100755 --- a/apps/plugins/lua/rbdefines_helper.pl +++ b/apps/plugins/lua/rbdefines_helper.pl @@ -57,13 +57,24 @@ if ($def_type eq "rb_defines") { '^CORE_KEYMAP_FILE$', 'CONTEXT_(STOPSEARCHING|REMOTE|CUSTOM|CUSTOM2|PLUGIN|REMAPPED)$', '^VIEWERS_DATA_DIR$'); + + +} +elsif ($def_type eq "audio_status") { + $lua_table = "rb"; + @rockbox_defines = ( + '^AUDIO_STATUS_(PLAY|PAUSE|RECORD|PRERECORD|ERROR|WARNING)$'); } elsif ($def_type eq "sound_defines") { $lua_table = "rb.sound_settings"; @rockbox_defines = ( '^(?!.*LAST_SETTING)SOUND_'); } - +elsif ($def_type eq "file_attrs") { + $lua_table = "rb"; + @rockbox_defines = ( + '^FILE_ATTR_'); +} my @captured_defines; my @names_seen; my @all_defines; @@ -100,7 +111,7 @@ while(my $line = ) $name = $1; next if $name =~ /^(ATTRIBUTE_|print|__).*/; #don't add reserved $value = $2; - + #print STDERR "#define ".$name." (".$value.")\n"; if(grep($name =~ $_, @rockbox_defines)) { push(@names_seen, $name); diff --git a/apps/plugins/lua/rockaux.c b/apps/plugins/lua/rockaux.c index 725e9c1d14..dd99ed1289 100644 --- a/apps/plugins/lua/rockaux.c +++ b/apps/plugins/lua/rockaux.c @@ -43,6 +43,7 @@ extern const char *strpbrk_n(const char *s, int smax, const char *accept); int errno = 0; #endif +#if (CONFIG_PLATFORM & PLATFORM_NATIVE) /* hosted platforms supply these */ char *strerror(int errnum) { (void) errnum; @@ -51,6 +52,7 @@ char *strerror(int errnum) return NULL; } +#endif /* splash string and allow user to scroll around * provides rudimentary text reflow @@ -239,10 +241,12 @@ long rb_pow(long x, long n) return pow; } +#if (CONFIG_PLATFORM & PLATFORM_NATIVE) /* hosted platforms supply these */ int strcoll(const char * str1, const char * str2) { return strcmp(str1, str2); } +#endif #if 0 //ndef _WIN32 /* supplied by strfrtime.lua*/ struct tm * gmtime(const time_t *timep) diff --git a/apps/plugins/lua/rocklibc.h b/apps/plugins/lua/rocklibc.h index 3dd093d2f3..eb34e1f147 100644 --- a/apps/plugins/lua/rocklibc.h +++ b/apps/plugins/lua/rocklibc.h @@ -52,7 +52,7 @@ extern int errno; #define open(...) rb->open(__VA_ARGS__) #define close(fd) rb->close(fd) #define lseek(fd,off,w) rb->lseek(fd,off,w) -#define filesize(fd) rb->filesize(fd) +#define ffilesize(fd) rb->ffilesize(fd) #define ftruncate(fd,l) rb->ftruncate(fd,l) #define file_exists(p) rb->file_exists(p) #define remove(f) rb->remove(f) @@ -71,7 +71,7 @@ extern int errno; #define open(...) PREFIX(open)(__VA_ARGS__) #define close(fd) PREFIX(close)(fd) #define lseek(fd,off,w) PREFIX(lseek)(fd,off,w) -#define filesize(fd) PREFIX(filesize)(fd) +#define ffilesize(fd) PREFIX(ffilesize)(fd) #define ftruncate(fd,l) PREFIX(ftruncate)(fd,l) #define file_exists(p) PREFIX(file_exists)(p) #define remove(f) PREFIX(remove)(f) diff --git a/apps/plugins/lua_scripts/file_browser.lua b/apps/plugins/lua_scripts/file_browser.lua index 8a16a1a544..0bcaed9101 100644 --- a/apps/plugins/lua_scripts/file_browser.lua +++ b/apps/plugins/lua_scripts/file_browser.lua @@ -34,6 +34,7 @@ package.path = scrpath .. "/?.lua;" .. package.path --add lua_scripts directory require("printmenus") --menu require("filebrowse") -- file browser +require("file_attrs") rb.actions = nil package.loaded["actions"] = nil @@ -45,22 +46,61 @@ function main_menu() [2] = "Sort by Name", [3] = "Sort by Size", [4] = "Sort by Date", - [5] = "Exit" + [5] = "Sort by Type", + [6] = "Exit" } local ft = { [0] = exit_now, --if user cancels do this function [1] = function(TITLE) return true end, -- shouldn't happen title occupies this slot [2] = function(SBNAME) - _lcd:splashf(rb.HZ, "%s", file_choose("/", "", "name", false) or "None") + _lcd:splashf(rb.HZ, "%s", file_choose("/", "", "name") or "None") end, [3] = function(SBSIZE) - _lcd:splashf(rb.HZ, "%s", file_choose("/", "", "size", true) or "None") + local file, attr = file_choose("/", "", "size", true, true) + if file and attr then + local sz = tonumber(string.match(attr, ".+Sz:(%d+)") or 0) + if sz > 1024 then + _lcd:splashf(rb.HZ, "%s", file .. " " .. sz / 1024 .. " kiB") + else + _lcd:splashf(rb.HZ, "%s", file .. " " .. sz .. " B") + end + else + _lcd:splashf(rb.HZ, "%s", file or "None") + end + --_lcd:splashf(rb.HZ, "%s", file_choose("/", "", "size") or "None") end, [4] = function(SBDATE) - _lcd:splashf(rb.HZ, "%s", file_choose("/", "", "date") or "None") + local file, attr = file_choose("/", "", "date", true, true) + if file and attr then + local tm = tonumber(string.match(attr, ".+Tm:(%d+)") or 0) + _lcd:splashf(rb.HZ, "%s", file .. " " .. attr)--" Tm:" .. tm) + else + _lcd:splashf(rb.HZ, "%s", file or "None") + end + --_lcd:splashf(rb.HZ, "%s", file_choose("/", "", "date") or "None") end, - [5] = function(EXIT_) return true end + [5] = function(SBTYPE) + local file = file_choose("/", "", "type") + if file then + local known = false + local attr = rb.filetype_get_attr(file) + for k, v in pairs(rb) do + if nil ~= string.find (k, "^FILE_ATTR_(.+)") then + if v == attr then + file = file .. " " .. k + known = true + break + end + end + end + if not known then + file = file .. " Unknown Type " .. string.match(file, "%.[^%.]+$") or "" + end + end + _lcd:splashf(rb.HZ, "%s", file or "None") + end, + [6] = function(EXIT_) return true end } print_menu(mt, ft) diff --git a/apps/plugins/lua_scripts/playback.lua b/apps/plugins/lua_scripts/playback.lua index 6dbba2bba7..2958a410f2 100644 --- a/apps/plugins/lua_scripts/playback.lua +++ b/apps/plugins/lua_scripts/playback.lua @@ -29,6 +29,7 @@ local _clr = require("color") require("actions") require("rbsettings") require("settings") +require("audio_status") local scrpath = rb.current_path() @@ -103,9 +104,9 @@ end local function clear_actions() track_length = (rb.audio("length") or 0) local playback = rb.audio("status") - if playback == 1 then + if playback == rb.AUDIO_STATUS_PLAY then set_active_icon(2) - elseif playback == 3 then + elseif playback == (rb.AUDIO_STATUS_PAUSE + rb.AUDIO_STATUS_PLAY) then set_active_icon(4) return end @@ -247,10 +248,10 @@ do end elseif action == act.PLA_SELECT then playback = rb.audio("status") - if playback == 1 then + if playback == rb.AUDIO_STATUS_PLAY then rb.audio("pause") collectgarbage("collect") - elseif playback == 3 then + elseif playback == (rb.AUDIO_STATUS_PAUSE + rb.AUDIO_STATUS_PLAY) then rb.audio("resume") end event = rb.audio("status") + 1 diff --git a/apps/plugins/midi/midiplay.c b/apps/plugins/midi/midiplay.c index 7f2c8ba2c6..ee5b1f13f9 100644 --- a/apps/plugins/midi/midiplay.c +++ b/apps/plugins/midi/midiplay.c @@ -316,7 +316,7 @@ #define MIDI_VOL_DOWN BUTTON_VOL_DOWN #define MIDI_PLAYPAUSE BUTTON_PLAY -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD /* use touchscreen */ #elif CONFIG_KEYPAD == MA_PAD @@ -335,7 +335,7 @@ #define MIDI_VOL_DOWN BUTTON_DOWN #define MIDI_PLAYPAUSE BUTTON_A -#elif CONFIG_KEYPAD == CTRU_PAD +#elif CONFIG_KEYPAD == N3DS_PAD #define MIDI_QUIT BUTTON_BACK #define MIDI_FFWD BUTTON_RIGHT #define MIDI_REWIND BUTTON_LEFT @@ -343,6 +343,14 @@ #define MIDI_VOL_DOWN BUTTON_DOWN #define MIDI_PLAYPAUSE BUTTON_USER +#elif CONFIG_KEYPAD == ECHO_R1_PAD +#define MIDI_QUIT BUTTON_POWER +#define MIDI_FFWD BUTTON_RIGHT +#define MIDI_REWIND BUTTON_LEFT +#define MIDI_VOL_UP BUTTON_UP +#define MIDI_VOL_DOWN BUTTON_DOWN +#define MIDI_PLAYPAUSE BUTTON_A + #else #error No keymap defined! #endif diff --git a/apps/plugins/mikmod/README.rockbox b/apps/plugins/mikmod/README.rockbox index 22f17b7b41..2e0cc923ab 100644 --- a/apps/plugins/mikmod/README.rockbox +++ b/apps/plugins/mikmod/README.rockbox @@ -1,7 +1,7 @@ mikmod (https://mikmod.sourceforge.net/) -Based on upstream commit 72424e1bebab470fbf8a2914dcd7903b742205e6 -corresponding to mikmod 3.2.9 / libmikmod 3.3.12 +Based on upstream commit 0c6c2f7f8fb8dd6954ab0407b400e9646b9c2cf0 +corresponding to pre mikmod 3.2.10 / libmikmod 3.3.14 Heavily customized for rockbox use: diff --git a/apps/plugins/mikmod/load_amf.c b/apps/plugins/mikmod/load_amf.c index 7d25430aac..aa7db9a5a9 100644 --- a/apps/plugins/mikmod/load_amf.c +++ b/apps/plugins/mikmod/load_amf.c @@ -424,8 +424,15 @@ static int AMF_Load(int curious) if(mh->version>=11) { memset(mh->panpos,0,32); _mm_read_SBYTES(mh->panpos,(mh->version>=13)?32:16,modreader); - } else if(mh->version>=9) + } else if(mh->version>=9) { _mm_read_UBYTES(channel_remap,16,modreader); + for(t = 0; t < 16 && t < mh->numchannels; t++) { + if(channel_remap[t] >= mh->numchannels) { + _mm_errno=MMERR_NOT_A_MODULE; + return 0; + } + } + } if (mh->version>=13) { mh->songbpm=_mm_read_UBYTE(modreader); diff --git a/apps/plugins/mikmod/load_dsm.c b/apps/plugins/mikmod/load_dsm.c index 97d54f429e..da8024f10d 100644 --- a/apps/plugins/mikmod/load_dsm.c +++ b/apps/plugins/mikmod/load_dsm.c @@ -238,6 +238,7 @@ static int DSM_Load(int curious) DSMINST s; SAMPLE *q; int cursmp=0,curpat=0,track=0; + unsigned int numtrk; (void)curious; blocklp=0; @@ -270,7 +271,12 @@ static int DSM_Load(int curious) of.modtype=MikMod_strdup(DSM_Version); of.numchn=mh->numtrk; of.numpat=mh->numpat; - of.numtrk=of.numchn*of.numpat; + numtrk=of.numchn*of.numpat; + if(of.numchn>DSM_MAXCHAN || numtrk>65535) { + _mm_errno = MMERR_LOADING_HEADER; + return 0; + } + of.numtrk=(UWORD)numtrk; of.songname=DupStr(mh->songname,28,1); /* make a cstr of songname */ of.reppos=0; of.flags |= UF_PANNING; diff --git a/apps/plugins/mikmod/load_imf.c b/apps/plugins/mikmod/load_imf.c index b67e3ac828..f580aac660 100644 --- a/apps/plugins/mikmod/load_imf.c +++ b/apps/plugins/mikmod/load_imf.c @@ -541,7 +541,6 @@ static int IMF_Load(int curious) _mm_read_I_UWORDS(ih.panenv,IMFENVCNT,modreader); _mm_read_I_UWORDS(ih.pitenv,IMFENVCNT,modreader); -#if defined __STDC__ || defined _MSC_VER || defined __WATCOMC__ || defined MPW_C #define IMF_FinishLoadingEnvelope(name) \ ih. name##pts=_mm_read_UBYTE(modreader); \ ih. name##sus=_mm_read_UBYTE(modreader); \ @@ -551,17 +550,6 @@ static int IMF_Load(int curious) _mm_skip_BYTE(modreader); \ _mm_skip_BYTE(modreader); \ _mm_skip_BYTE(modreader) -#else -#define IMF_FinishLoadingEnvelope(name) \ - ih. name/**/pts=_mm_read_UBYTE(modreader); \ - ih. name/**/sus=_mm_read_UBYTE(modreader); \ - ih. name/**/beg=_mm_read_UBYTE(modreader); \ - ih. name/**/end=_mm_read_UBYTE(modreader); \ - ih. name/**/flg=_mm_read_UBYTE(modreader); \ - _mm_skip_BYTE(modreader); \ - _mm_skip_BYTE(modreader); \ - _mm_skip_BYTE(modreader) -#endif IMF_FinishLoadingEnvelope(vol); IMF_FinishLoadingEnvelope(pan); @@ -594,7 +582,6 @@ static int IMF_Load(int curious) d->samplenumber[u]=ih.what[u]>ih.numsmp?0xffff:ih.what[u]+of.numsmp; d->volfade=ih.volfade; -#if defined __STDC__ || defined _MSC_VER || defined __WATCOMC__ || defined MPW_C #define IMF_ProcessEnvelope(name) \ for (u = 0; u < (IMFENVCNT >> 1); u++) { \ d-> name##env[u].pos = ih. name##env[u << 1]; \ @@ -610,23 +597,6 @@ static int IMF_Load(int curious) \ if ((d-> name##flg&EF_ON)&&(d-> name##pts<2)) \ d-> name##flg&=~EF_ON -#else -#define IMF_ProcessEnvelope(name) \ - for (u = 0; u < (IMFENVCNT >> 1); u++) { \ - d-> name/**/env[u].pos = ih. name/**/env[u << 1]; \ - d-> name/**/env[u].val = ih. name/**/env[(u << 1)+ 1]; \ - } \ - if (ih. name/**/flg&1) d-> name/**/flg|=EF_ON; \ - if (ih. name/**/flg&2) d-> name/**/flg|=EF_SUSTAIN; \ - if (ih. name/**/flg&4) d-> name/**/flg|=EF_LOOP; \ - d-> name/**/susbeg=d-> name/**/susend=ih. name/**/sus; \ - d-> name/**/beg=ih. name/**/beg; \ - d-> name/**/end=ih. name/**/end; \ - d-> name/**/pts=ih. name/**/pts; \ - \ - if ((d-> name/**/flg&EF_ON)&&(d-> name/**/pts<2)) \ - d-> name/**/flg&=~EF_ON -#endif IMF_ProcessEnvelope(vol); IMF_ProcessEnvelope(pan); diff --git a/apps/plugins/mikmod/load_it.c b/apps/plugins/mikmod/load_it.c index 4442c209c0..7fa2495db5 100644 --- a/apps/plugins/mikmod/load_it.c +++ b/apps/plugins/mikmod/load_it.c @@ -194,7 +194,7 @@ static int IT_Init(void) { if(!(mh=(ITHEADER*)MikMod_malloc(sizeof(ITHEADER)))) return 0; if(!(poslookup=(UBYTE*)MikMod_malloc(256*sizeof(UBYTE)))) return 0; - if(!(itpat=(ITNOTE*)MikMod_malloc(200*64*sizeof(ITNOTE)))) return 0; + if(!(itpat=(ITNOTE*)MikMod_malloc(256*64*sizeof(ITNOTE)))) return 0; if(!(mask=(UBYTE*)MikMod_calloc(64,sizeof(UBYTE)))) return 0; if(!(last=(ITNOTE*)MikMod_calloc(64,sizeof(ITNOTE)))) return 0; @@ -338,10 +338,10 @@ static int IT_ReadPattern(UWORD patrows) int row=0,flag,ch; unsigned int blah; ITNOTE *itt=itpat,dummy,*n,*l; - ITNOTE *ite=&itpat[200*64 -1]; + ITNOTE *ite=&itpat[256*64 -1]; UBYTE *m; - memset(itt,255,200*64*sizeof(ITNOTE)); + memset(itt,255,256*64*sizeof(ITNOTE)); do { if(_mm_eof(modreader)) { @@ -783,7 +783,6 @@ static int IT_Load(int curious) } } else { /* load IT 2xx volume, pan and pitch envelopes */ -#if defined __STDC__ || defined _MSC_VER || defined __WATCOMC__ || defined MPW_C #define IT_LoadEnvelope(name,type) \ ih. name##flg =_mm_read_UBYTE(modreader); \ ih. name##pts =_mm_read_UBYTE(modreader); \ @@ -798,22 +797,6 @@ static int IT_Load(int curious) ih. name##tick[lp]=_mm_read_I_UWORD(modreader); \ } \ _mm_skip_BYTE(modreader) -#else -#define IT_LoadEnvelope(name,type) \ - ih. name/**/flg =_mm_read_UBYTE(modreader); \ - ih. name/**/pts =_mm_read_UBYTE(modreader); \ - if (ih. name/**/pts > ITENVCNT) \ - ih. name/**/pts = ITENVCNT; \ - ih. name/**/beg =_mm_read_UBYTE(modreader); \ - ih. name/**/end =_mm_read_UBYTE(modreader); \ - ih. name/**/susbeg=_mm_read_UBYTE(modreader); \ - ih. name/**/susend=_mm_read_UBYTE(modreader); \ - for(lp=0;lprpanvar = ih.rpanvar; } -#if defined __STDC__ || defined _MSC_VER || defined __WATCOMC__ || defined MPW_C #define IT_ProcessEnvelope(name) \ if(ih. name##flg&1) d-> name##flg|=EF_ON; \ if(ih. name##flg&2) d-> name##flg|=EF_LOOP; \ @@ -891,23 +873,6 @@ static int IT_Load(int curious) \ if((d-> name##flg&EF_ON)&&(d-> name##pts<2)) \ d-> name##flg&=~EF_ON -#else -#define IT_ProcessEnvelope(name) \ - if(ih. name/**/flg&1) d-> name/**/flg|=EF_ON; \ - if(ih. name/**/flg&2) d-> name/**/flg|=EF_LOOP; \ - if(ih. name/**/flg&4) d-> name/**/flg|=EF_SUSTAIN; \ - d-> name/**/pts=ih. name/**/pts; \ - d-> name/**/beg=ih. name/**/beg; \ - d-> name/**/end=ih. name/**/end; \ - d-> name/**/susbeg=ih. name/**/susbeg; \ - d-> name/**/susend=ih. name/**/susend; \ - \ - for(u=0;u name/**/env[u].pos=ih. name/**/tick[u]; \ - \ - if((d-> name/**/flg&EF_ON)&&(d-> name/**/pts<2)) \ - d-> name/**/flg&=~EF_ON -#endif IT_ProcessEnvelope(vol); diff --git a/apps/plugins/mikmod/load_okt.c b/apps/plugins/mikmod/load_okt.c index af3b7c53de..516282e7b0 100644 --- a/apps/plugins/mikmod/load_okt.c +++ b/apps/plugins/mikmod/load_okt.c @@ -282,7 +282,12 @@ static int OKT_doPBOD(int patnum) int u; if (!patnum) { - of.numtrk = of.numpat * of.numchn; + const unsigned int numtrk = of.numpat * of.numchn; + if (numtrk > 65535) { + _mm_errno = MMERR_LOADING_PATTERN; + return 0; + } + of.numtrk = (UWORD)numtrk; if (!AllocTracks() || !AllocPatterns()) return 0; diff --git a/apps/plugins/mikmod/load_ult.c b/apps/plugins/mikmod/load_ult.c index 58c66d16aa..0c695659a7 100644 --- a/apps/plugins/mikmod/load_ult.c +++ b/apps/plugins/mikmod/load_ult.c @@ -125,6 +125,7 @@ static UBYTE ReadUltEvent(ULTEVENT* event) static int ULT_Load(int curious) { int t,u,tracks=0; + unsigned int numtrk; SAMPLE *q; ULTSAMPLE s; ULTHEADER mh; @@ -211,9 +212,14 @@ static int ULT_Load(int curious) noc=_mm_read_UBYTE(modreader); RBnop=_mm_read_UBYTE(modreader); - of.numchn=++noc; - of.numpat=++RBnop; - of.numtrk=of.numchn*of.numpat; + of.numchn=noc+1; + of.numpat=RBnop+1; + numtrk=of.numchn*of.numpat; + if (numtrk>65535) { + _mm_errno = MMERR_LOADING_HEADER; + return 0; + } + of.numtrk=(UWORD)numtrk; for(t=0;t<256;t++) { if(of.positions[t]==255) { diff --git a/apps/plugins/mikmod/load_uni.c b/apps/plugins/mikmod/load_uni.c index 3a911053f9..fdd689b183 100644 --- a/apps/plugins/mikmod/load_uni.c +++ b/apps/plugins/mikmod/load_uni.c @@ -307,7 +307,6 @@ static int loadinstr6(void) i->rpanvar = _mm_read_UBYTE(modreader); i->volfade = _mm_read_M_UWORD(modreader); -#if defined __STDC__ || defined _MSC_VER || defined __WATCOMC__ || defined MPW_C #define UNI_LoadEnvelope6(name) \ i-> name##flg=_mm_read_UBYTE(modreader); \ i-> name##pts=_mm_read_UBYTE(modreader); \ @@ -315,23 +314,11 @@ static int loadinstr6(void) i-> name##susend=_mm_read_UBYTE(modreader); \ i-> name##beg=_mm_read_UBYTE(modreader); \ i-> name##end=_mm_read_UBYTE(modreader); \ + if (i-> name##pts > ENVPOINTS) goto fail; \ for(w=0;w<(universion>=0x100?32:i-> name##pts);w++) { \ i-> name##env[w].pos=_mm_read_M_SWORD(modreader); \ i-> name##env[w].val=_mm_read_M_SWORD(modreader); \ } -#else -#define UNI_LoadEnvelope6(name) \ - i-> name/**/flg=_mm_read_UBYTE(modreader); \ - i-> name/**/pts=_mm_read_UBYTE(modreader); \ - i-> name/**/susbeg=_mm_read_UBYTE(modreader); \ - i-> name/**/susend=_mm_read_UBYTE(modreader); \ - i-> name/**/beg=_mm_read_UBYTE(modreader); \ - i-> name/**/end=_mm_read_UBYTE(modreader); \ - for (w=0;w<(universion>=0x100?32:i-> name/**/pts);w++) { \ - i-> name/**/env[w].pos=_mm_read_M_SWORD(modreader); \ - i-> name/**/env[w].val=_mm_read_M_SWORD(modreader); \ - } -#endif UNI_LoadEnvelope6(vol); UNI_LoadEnvelope6(pan); @@ -348,6 +335,7 @@ static int loadinstr6(void) i->insname=readstring(); if(_mm_eof(modreader)) { + fail: _mm_errno = MMERR_LOADING_SAMPLEINFO; return 0; } @@ -372,7 +360,6 @@ static int loadinstr5(void) for(u=0;u<96;u++) i->samplenumber[u]=of.numsmp+_mm_read_UBYTE(modreader); -#if defined __STDC__ || defined _MSC_VER || defined __WATCOMC__ || defined MPW_C #define UNI_LoadEnvelope5(name) \ i-> name##flg=_mm_read_UBYTE(modreader); \ i-> name##pts=_mm_read_UBYTE(modreader); \ @@ -384,19 +371,6 @@ static int loadinstr5(void) i-> name##env[u].pos=_mm_read_I_SWORD(modreader); \ i-> name##env[u].val=_mm_read_I_SWORD(modreader); \ } -#else -#define UNI_LoadEnvelope5(name) \ - i-> name/**/flg=_mm_read_UBYTE(modreader); \ - i-> name/**/pts=_mm_read_UBYTE(modreader); \ - i-> name/**/susbeg=_mm_read_UBYTE(modreader); \ - i-> name/**/susend=i-> name/**/susbeg; \ - i-> name/**/beg=_mm_read_UBYTE(modreader); \ - i-> name/**/end=_mm_read_UBYTE(modreader); \ - for(u=0;u<12;u++) { \ - i-> name/**/env[u].pos=_mm_read_I_SWORD(modreader); \ - i-> name/**/env[u].val=_mm_read_I_SWORD(modreader); \ - } -#endif UNI_LoadEnvelope5(vol); UNI_LoadEnvelope5(pan); @@ -619,6 +593,10 @@ static int UNI_Load(int curious) /* positions */ if(!AllocPositions(of.numpos)) return 0; if(universion>=6) { + if(mh.numchn>UF_MAXCHAN) { + _mm_errno=MMERR_LOADING_HEADER; + return 0; + } if(universion>=0x100) _mm_read_M_UWORDS(of.positions,of.numpos,modreader); else diff --git a/apps/plugins/mikmod/load_xm.c b/apps/plugins/mikmod/load_xm.c index 3dedfffbe5..13208d8698 100644 --- a/apps/plugins/mikmod/load_xm.c +++ b/apps/plugins/mikmod/load_xm.c @@ -533,7 +533,6 @@ static int LoadInstruments(void) d->samplenumber[u]=pth.what[u]+of.numsmp; d->volfade = pth.volfade; -#if defined __STDC__ || defined _MSC_VER || defined __WATCOMC__ || defined MPW_C #define XM_ProcessEnvelope(name) \ for (u = 0; u < XMENVPTS; u++) { \ d-> name##env[u].pos = pth. name##env[u << 1]; \ @@ -553,28 +552,6 @@ static int LoadInstruments(void) \ if ((d-> name##flg&EF_ON)&&(d-> name##pts<2)) \ d-> name##flg&=~EF_ON -#else -#define XM_ProcessEnvelope(name) \ - for (u = 0; u < XMENVPTS; u++) { \ - d-> name/**/env[u].pos = pth. name/**/env[u << 1]; \ - d-> name/**/env[u].val = pth. name/**/env[(u << 1)+ 1]; \ - } \ - if (pth. name/**/flg&1) d-> name/**/flg|=EF_ON; \ - if (pth. name/**/flg&2) d-> name/**/flg|=EF_SUSTAIN; \ - if (pth. name/**/flg&4) d-> name/**/flg|=EF_LOOP; \ - d-> name/**/susbeg=d-> name/**/susend= \ - pth. name/**/sus; \ - d-> name/**/beg=pth. name/**/beg; \ - d-> name/**/end=pth. name/**/end; \ - d-> name/**/pts=pth. name/**/pts; \ - \ - /* scale envelope */ \ - for (p = 0; p < XMENVPTS; p++) \ - d-> name/**/env[p].val<<=2; \ - \ - if ((d-> name/**/flg&EF_ON)&&(d-> name/**/pts<2)) \ - d-> name/**/flg&=~EF_ON -#endif XM_ProcessEnvelope(vol); XM_ProcessEnvelope(pan); diff --git a/apps/plugins/mikmod/mdreg.c b/apps/plugins/mikmod/mdreg.c index ff5d803000..b0ec4cf2fd 100644 --- a/apps/plugins/mikmod/mdreg.c +++ b/apps/plugins/mikmod/mdreg.c @@ -93,6 +93,9 @@ static void _mm_registeralldrivers(void) #ifdef DRV_OS2 _mm_registerdriver(&drv_os2); #endif +#ifdef DRV_WASAPI + _mm_registerdriver(&drv_wasapi); +#endif #ifdef DRV_XAUDIO2 _mm_registerdriver(&drv_xaudio2); #endif @@ -123,6 +126,9 @@ static void _mm_registeralldrivers(void) #ifdef DRV_N64 _mm_registerdriver(&drv_n64); #endif +#ifdef DRV_VITA + _mm_registerdriver(&drv_vita); +#endif /* dos drivers - wss first, since some cards emulate sb */ #ifdef DRV_WSS diff --git a/apps/plugins/mikmod/mikmod.c b/apps/plugins/mikmod/mikmod.c index a482ace68b..81bc2c4413 100644 --- a/apps/plugins/mikmod/mikmod.c +++ b/apps/plugins/mikmod/mikmod.c @@ -6,7 +6,7 @@ #undef SYNC -#if defined(SIMULATOR) || defined(CTRU) +#if defined(SIMULATOR) || defined(N3DS) #define SYNC #else #define USETHREADS diff --git a/apps/plugins/mikmod/mikmod.h b/apps/plugins/mikmod/mikmod.h index 749b535bdc..343ca5073b 100644 --- a/apps/plugins/mikmod/mikmod.h +++ b/apps/plugins/mikmod/mikmod.h @@ -145,13 +145,13 @@ typedef unsigned short int UWORD; #if defined(_LP64) || defined(__LP64__) || defined(__arch64__) || defined(__alpha) || defined(__x86_64) || defined(__powerpc64__) /* 64 bit architectures: */ typedef signed int SLONG; -#if !defined(_MIKMOD_AMIGA) && !defined(WIN32) +#if !(defined(_WIN32) || defined(_MIKMOD_AMIGA)) typedef unsigned int ULONG; #endif #else /* 32 bit architectures: */ typedef signed long int SLONG; -#if !(defined(_MIKMOD_OS2) || defined(_MIKMOD_AMIGA)) +#if !(defined(_MIKMOD_OS2) || defined(_MIKMOD_WIN32) || defined(_MIKMOD_AMIGA)) typedef unsigned long int ULONG; #endif #endif @@ -293,6 +293,7 @@ enum { MMERR_MAX }; +#define MMERR_WASAPI_SAMPLERATE MMERR_SGI_SPEED /* * ========== Error handling @@ -826,6 +827,7 @@ MIKMODAPI extern struct MDRIVER drv_dart; /* OS/2 Direct Audio RealTime */ MIKMODAPI extern struct MDRIVER drv_os2; /* OS/2 MMPM/2 */ MIKMODAPI extern struct MDRIVER drv_ds; /* Win32 DirectSound driver */ +MIKMODAPI extern struct MDRIVER drv_wasapi; /* Win32 WASAPI driver */ MIKMODAPI extern struct MDRIVER drv_xaudio2;/* Win32 XAudio2 driver */ MIKMODAPI extern struct MDRIVER drv_win; /* Win32 multimedia API driver */ @@ -836,6 +838,7 @@ MIKMODAPI extern struct MDRIVER drv_dc; /* Dreamcast driver */ MIKMODAPI extern struct MDRIVER drv_gp32; /* GP32 Sound driver */ MIKMODAPI extern struct MDRIVER drv_psp; /* PlayStation Portable driver */ MIKMODAPI extern struct MDRIVER drv_n64; /* Nintendo64 driver */ +MIKMODAPI extern struct MDRIVER drv_vita; /* PlayStation Vita driver */ MIKMODAPI extern struct MDRIVER drv_wss; /* DOS WSS driver */ MIKMODAPI extern struct MDRIVER drv_sb; /* DOS S/B driver */ diff --git a/apps/plugins/mikmod/mikmod_internals.h b/apps/plugins/mikmod/mikmod_internals.h index f7ca8a1741..0f1d9613a4 100644 --- a/apps/plugins/mikmod/mikmod_internals.h +++ b/apps/plugins/mikmod/mikmod_internals.h @@ -33,6 +33,14 @@ extern "C" { #include +#if defined(_MSC_VER) +#pragma warning(disable:4389) /* signed/unsigned mismatch ( <, <=, >, >= ) */ +#pragma warning(disable:4018) /* signed/unsigned mismatch ( ==, != ) */ +#pragma warning(disable:4761) /* integral size mismatch in argument; conversion supplied (for MSVC6 and older.) */ +#pragma warning(disable:4244) /* conversion from 'type' to 'int', possible loss of data */ +#pragma warning(disable:4267) /* conversion from 'size_t' to 'type', possible loss of data */ +#endif + #if defined(_MSC_VER) && !defined(__cplusplus) && !defined(HAVE_CONFIG_H) #define inline __inline #endif @@ -902,5 +910,3 @@ void MikMod_afree(void *); /* frees if ptr != NULL */ #endif #endif /* _MIKMOD_INTERNALS_H */ - -/* ex:set ts=4: */ diff --git a/apps/plugins/mikmod/mikmod_supp.h b/apps/plugins/mikmod/mikmod_supp.h index f7d8d26d2a..a82670bf15 100644 --- a/apps/plugins/mikmod/mikmod_supp.h +++ b/apps/plugins/mikmod/mikmod_supp.h @@ -46,8 +46,8 @@ #define read(a,b,c) rb->read(a,b,c) #undef write #define write(a,b,c) rb->write(a,b,c) -#undef filesize -#define filesize(a) rb->filesize(a) +#undef ffilesize +#define ffilesize(a) rb->ffilesize(a) #endif #define malloc(x) tlsf_malloc(x) diff --git a/apps/plugins/mikmod/mmerror.c b/apps/plugins/mikmod/mmerror.c index ee7340ee27..99c4575022 100644 --- a/apps/plugins/mikmod/mmerror.c +++ b/apps/plugins/mikmod/mmerror.c @@ -132,14 +132,14 @@ static const char *_mm_errmsg[MMERR_MAX+1] = #endif /* SGI driver errors */ -#ifdef DRV_SGI "Unsupported sample rate", +#ifdef DRV_SGI "Hardware does not support 16 bit sound", "Hardware does not support 8 bit sound", "Hardware does not support stereo sound", "Hardware does not support mono sound", #else - _mmerr_invalid, _mmerr_invalid, _mmerr_invalid, + _mmerr_invalid, _mmerr_invalid, _mmerr_invalid, _mmerr_invalid, #endif diff --git a/apps/plugins/mikmod/mmio.c b/apps/plugins/mikmod/mmio.c index 6e317ca008..ef3d44d756 100644 --- a/apps/plugins/mikmod/mmio.c +++ b/apps/plugins/mikmod/mmio.c @@ -133,7 +133,7 @@ typedef struct MFILEREADER { static int _mm_FileReader_Eof(MREADER* reader) { //return feof(((MFILEREADER*)reader)->file); - int size = filesize(((MFILEREADER*)reader)->file); + int size = ffilesize(((MFILEREADER*)reader)->file); int offset = lseek(((MFILEREADER*)reader)->file, 0, SEEK_CUR); return (size <= 0 || offset < 0 || offset >= size) ? 1 : 0; } diff --git a/apps/plugins/mikmod/virtch_common.c b/apps/plugins/mikmod/virtch_common.c index 70cd9417b2..dd665a1504 100644 --- a/apps/plugins/mikmod/virtch_common.c +++ b/apps/plugins/mikmod/virtch_common.c @@ -107,7 +107,6 @@ static int (*VC_VoiceStopped_ptr)(UBYTE); static SLONG (*VC_VoiceGetPosition_ptr)(UBYTE); static ULONG (*VC_VoiceRealVolume_ptr)(UBYTE); -#if defined __STDC__ || defined _MSC_VER || defined __WATCOMC__ || defined MPW_C #define VC_PROC0(suffix) \ MIKMODAPI void VC_##suffix (void) { VC_##suffix##_ptr(); } @@ -126,27 +125,6 @@ MIKMODAPI void VC_##suffix (typ1 a,typ2 b) { VC_##suffix##_ptr(a,b); } #define VC_FUNC2(suffix,ret,typ1,typ2) \ MIKMODAPI ret VC_##suffix (typ1 a,typ2 b) { return VC_##suffix##_ptr(a,b); } -#else - -#define VC_PROC0(suffix) \ -MIKMODAPI void VC_/**/suffix (void) { VC_/**/suffix/**/_ptr(); } - -#define VC_FUNC0(suffix,ret) \ -MIKMODAPI ret VC_/**/suffix (void) { return VC_/**/suffix/**/_ptr(); } - -#define VC_PROC1(suffix,typ1) \ -MIKMODAPI void VC_/**/suffix (typ1 a) { VC_/**/suffix/**/_ptr(a); } - -#define VC_FUNC1(suffix,ret,typ1) \ -MIKMODAPI ret VC_/**/suffix (typ1 a) { return VC_/**/suffix/**/_ptr(a); } - -#define VC_PROC2(suffix,typ1,typ2) \ -MIKMODAPI void VC_/**/suffix (typ1 a,typ2 b) { VC_/**/suffix/**/_ptr(a,b); } - -#define VC_FUNC2(suffix,ret,typ1,typ2) \ -MIKMODAPI ret VC_/**/suffix (typ1 a,typ2 b) { return VC_/**/suffix/**/_ptr(a,b); } -#endif - VC_FUNC0(Init,int) VC_PROC0(Exit) VC_FUNC0(SetNumVoices,int) diff --git a/apps/plugins/minesweeper.c b/apps/plugins/minesweeper.c index 0faff83f33..6e50e12ab0 100644 --- a/apps/plugins/minesweeper.c +++ b/apps/plugins/minesweeper.c @@ -441,7 +441,7 @@ CONFIG_KEYPAD == MROBE500_PAD # define MINESWP_DISCOVER (BUTTON_MENU|BUTTON_REPEAT) # define MINESWP_INFO BUTTON_MENU -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD /* use touchscreen */ #elif CONFIG_KEYPAD == RG_NANO_PAD @@ -454,7 +454,7 @@ CONFIG_KEYPAD == MROBE500_PAD # define MINESWP_DISCOVER BUTTON_A # define MINESWP_INFO BUTTON_Y -#elif CONFIG_KEYPAD == CTRU_PAD +#elif CONFIG_KEYPAD == N3DS_PAD # define MINESWP_LEFT BUTTON_LEFT # define MINESWP_RIGHT BUTTON_RIGHT # define MINESWP_UP BUTTON_UP @@ -464,6 +464,16 @@ CONFIG_KEYPAD == MROBE500_PAD # define MINESWP_DISCOVER BUTTON_SELECT # define MINESWP_INFO BUTTON_MENU +#elif CONFIG_KEYPAD == ECHO_R1_PAD +# define MINESWP_LEFT BUTTON_LEFT +# define MINESWP_RIGHT BUTTON_RIGHT +# define MINESWP_UP BUTTON_UP +# define MINESWP_DOWN BUTTON_DOWN +# define MINESWP_QUIT BUTTON_POWER +# define MINESWP_TOGGLE BUTTON_B +# define MINESWP_DISCOVER BUTTON_A +# define MINESWP_INFO BUTTON_START + #else #error No keymap defined! #endif diff --git a/apps/plugins/mp3_encoder.c b/apps/plugins/mp3_encoder.c index b5790f3ae2..5d3c525e8c 100644 --- a/apps/plugins/mp3_encoder.c +++ b/apps/plugins/mp3_encoder.c @@ -906,7 +906,7 @@ static int wave_open(void) (cfg.samplerate != 44100) && (cfg.samplerate != 48000)) return -9; header_size = 0x28; - wav_size = rb->filesize(wavfile); + wav_size = rb->ffilesize(wavfile); rb->lseek(wavfile, header_size, SEEK_SET); return 0; @@ -2578,7 +2578,7 @@ CONFIG_KEYPAD == MROBE500_PAD #define MP3ENC_DONE BUTTON_POWER #define MP3ENC_SELECT BUTTON_SELECT -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD /* use touchscreen */ #elif CONFIG_KEYPAD == MA_PAD @@ -2593,12 +2593,18 @@ CONFIG_KEYPAD == MROBE500_PAD #define MP3ENC_DONE BUTTON_START #define MP3ENC_SELECT BUTTON_A -#elif CONFIG_KEYPAD == CTRU_PAD +#elif CONFIG_KEYPAD == N3DS_PAD #define MP3ENC_PREV BUTTON_UP #define MP3ENC_NEXT BUTTON_DOWN #define MP3ENC_DONE BUTTON_POWER #define MP3ENC_SELECT BUTTON_SELECT +#elif CONFIG_KEYPAD == ECHO_R1_PAD +#define MP3ENC_PREV BUTTON_UP +#define MP3ENC_NEXT BUTTON_DOWN +#define MP3ENC_DONE BUTTON_POWER +#define MP3ENC_SELECT BUTTON_RIGHT + #else #error No keymap defined! #endif diff --git a/apps/plugins/mpegplayer/disk_buf.c b/apps/plugins/mpegplayer/disk_buf.c index 9f55f57ee0..1d1d1695a9 100644 --- a/apps/plugins/mpegplayer/disk_buf.c +++ b/apps/plugins/mpegplayer/disk_buf.c @@ -875,7 +875,7 @@ int disk_buf_open(const char *filename) if (fd >= 0) { - ssize_t filesize = rb->filesize(fd); + ssize_t filesize = rb->ffilesize(fd); if (filesize <= 0) { diff --git a/apps/plugins/mpegplayer/mpeg_settings.c b/apps/plugins/mpegplayer/mpeg_settings.c index 3dc621d2e9..c3db0336ec 100644 --- a/apps/plugins/mpegplayer/mpeg_settings.c +++ b/apps/plugins/mpegplayer/mpeg_settings.c @@ -352,7 +352,7 @@ struct mpeg_settings settings; #define MPEG_START_TIME_DOWN BUTTON_DOWN #define MPEG_START_TIME_EXIT BUTTON_BACK -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD #define MPEG_START_TIME_EXIT BUTTON_POWER #elif CONFIG_KEYPAD == RG_NANO_PAD @@ -365,7 +365,7 @@ struct mpeg_settings settings; #define MPEG_START_TIME_DOWN BUTTON_DOWN #define MPEG_START_TIME_EXIT BUTTON_START -#elif CONFIG_KEYPAD == CTRU_PAD +#elif CONFIG_KEYPAD == N3DS_PAD #define MPEG_START_TIME_SELECT BUTTON_SELECT #define MPEG_START_TIME_LEFT BUTTON_LEFT #define MPEG_START_TIME_RIGHT BUTTON_RIGHT @@ -373,6 +373,14 @@ struct mpeg_settings settings; #define MPEG_START_TIME_DOWN BUTTON_DOWN #define MPEG_START_TIME_EXIT BUTTON_BACK +#elif CONFIG_KEYPAD == ECHO_R1_PAD +#define MPEG_START_TIME_SELECT BUTTON_A +#define MPEG_START_TIME_LEFT BUTTON_LEFT +#define MPEG_START_TIME_RIGHT BUTTON_RIGHT +#define MPEG_START_TIME_UP BUTTON_UP +#define MPEG_START_TIME_DOWN BUTTON_DOWN +#define MPEG_START_TIME_EXIT BUTTON_B + #else #error No keymap defined! #endif diff --git a/apps/plugins/mpegplayer/mpegplayer.c b/apps/plugins/mpegplayer/mpegplayer.c index fcbd872125..869d691574 100644 --- a/apps/plugins/mpegplayer/mpegplayer.c +++ b/apps/plugins/mpegplayer/mpegplayer.c @@ -485,7 +485,7 @@ CONFIG_KEYPAD == SANSA_M200_PAD #define MPEG_RW BUTTON_LEFT #define MPEG_FF BUTTON_RIGHT -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD /* use touchscreen */ #elif CONFIG_KEYPAD == RG_NANO_PAD @@ -497,7 +497,7 @@ CONFIG_KEYPAD == SANSA_M200_PAD #define MPEG_RW BUTTON_LEFT #define MPEG_FF BUTTON_RIGHT -#elif CONFIG_KEYPAD == CTRU_PAD +#elif CONFIG_KEYPAD == N3DS_PAD #define MPEG_MENU BUTTON_MENU #define MPEG_PAUSE BUTTON_SELECT #define MPEG_STOP BUTTON_POWER @@ -506,6 +506,15 @@ CONFIG_KEYPAD == SANSA_M200_PAD #define MPEG_RW BUTTON_LEFT #define MPEG_FF BUTTON_RIGHT +#elif CONFIG_KEYPAD == ECHO_R1_PAD +#define MPEG_MENU BUTTON_UP +#define MPEG_PAUSE BUTTON_DOWN +#define MPEG_STOP BUTTON_POWER +#define MPEG_VOLDOWN BUTTON_VOL_DOWN +#define MPEG_VOLUP BUTTON_VOL_UP +#define MPEG_RW BUTTON_LEFT +#define MPEG_FF BUTTON_RIGHT + #else #error No keymap defined! #endif diff --git a/apps/plugins/open_plugins.c b/apps/plugins/open_plugins.c index 1a10c797e9..84a2187f88 100644 --- a/apps/plugins/open_plugins.c +++ b/apps/plugins/open_plugins.c @@ -191,7 +191,7 @@ static int op_entry_read_opx(const char *path) fd_opx = rb->open(path, O_RDONLY); if (fd_opx >= 0) { - filesize = rb->filesize(fd_opx); + filesize = rb->ffilesize(fd_opx); ret = filesize; if (filesize == op_entry_sz && !op_entry_read(fd_opx, 0, op_entry_sz)) ret = 0; diff --git a/apps/plugins/oscilloscope.c b/apps/plugins/oscilloscope.c index 8888bdc356..d8b3c74eaa 100644 --- a/apps/plugins/oscilloscope.c +++ b/apps/plugins/oscilloscope.c @@ -549,7 +549,7 @@ #define OSCILLOSCOPE_VOL_UP BUTTON_UP #define OSCILLOSCOPE_VOL_DOWN BUTTON_DOWN -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD /* use touchscreen */ #elif CONFIG_KEYPAD == RG_NANO_PAD @@ -564,7 +564,7 @@ #define OSCILLOSCOPE_VOL_UP BUTTON_UP #define OSCILLOSCOPE_VOL_DOWN BUTTON_DOWN -#elif CONFIG_KEYPAD == CTRU_PAD +#elif CONFIG_KEYPAD == N3DS_PAD #define OSCILLOSCOPE_QUIT BUTTON_BACK #define OSCILLOSCOPE_DRAWMODE BUTTON_USER #define OSCILLOSCOPE_ADVMODE BUTTON_MENU @@ -575,6 +575,17 @@ #define OSCILLOSCOPE_VOL_UP BUTTON_UP #define OSCILLOSCOPE_VOL_DOWN BUTTON_DOWN +#elif CONFIG_KEYPAD == ECHO_R1_PAD +#define OSCILLOSCOPE_QUIT BUTTON_POWER +#define OSCILLOSCOPE_DRAWMODE BUTTON_SELECT +#define OSCILLOSCOPE_ADVMODE BUTTON_START +#define OSCILLOSCOPE_ORIENTATION BUTTON_B +#define OSCILLOSCOPE_PAUSE BUTTON_A +#define OSCILLOSCOPE_SPEED_UP BUTTON_RIGHT +#define OSCILLOSCOPE_SPEED_DOWN BUTTON_LEFT +#define OSCILLOSCOPE_VOL_UP BUTTON_UP +#define OSCILLOSCOPE_VOL_DOWN BUTTON_DOWN + #else #error No keymap defined! #endif diff --git a/apps/plugins/pacbox/pacbox.h b/apps/plugins/pacbox/pacbox.h index e7cef86453..86fbeb8ef1 100644 --- a/apps/plugins/pacbox/pacbox.h +++ b/apps/plugins/pacbox/pacbox.h @@ -386,7 +386,7 @@ #define PACMAN_1UP BUTTON_VOL_UP #define PACMAN_COIN BUTTON_PLAY -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD #define PACMAN_UP BUTTON_TOPMIDDLE #define PACMAN_DOWN BUTTON_BOTTOMMIDDLE #define PACMAN_LEFT BUTTON_MIDLEFT @@ -417,7 +417,7 @@ #define PACMAN_COIN BUTTON_A #define PACMAN_MENU BUTTON_START -#elif CONFIG_KEYPAD == CTRU_PAD +#elif CONFIG_KEYPAD == N3DS_PAD #define PACMAN_UP BUTTON_UP #define PACMAN_DOWN BUTTON_DOWN @@ -428,6 +428,17 @@ #define PACMAN_COIN BUTTON_USER #define PACMAN_MENU BUTTON_MENU +#elif CONFIG_KEYPAD == ECHO_R1_PAD + +#define PACMAN_UP BUTTON_RIGHT +#define PACMAN_DOWN BUTTON_LEFT +#define PACMAN_LEFT BUTTON_UP +#define PACMAN_RIGHT BUTTON_DOWN +#define PACMAN_1UP BUTTON_SELECT +#define PACMAN_2UP BUTTON_START +#define PACMAN_COIN BUTTON_X +#define PACMAN_MENU BUTTON_VOL_UP + #else #error Keymap not defined! @@ -533,7 +544,7 @@ NOTE: pacbox.c assumes this is an integer divisor of 60 */ #if defined(TOSHIBA_GIGABEAT_S) || defined (TOSHIBA_GIGABEAT_F) || \ - defined(SANSA_FUZEPLUS) + defined(SANSA_FUZEPLUS) || defined(ECHO_R1) /* Gigabeat S,F and Sansa Fuze+ can manage the full framerate (1 in 1 frames) */ #define FPS 60 diff --git a/apps/plugins/pacbox/pacbox_arm.S b/apps/plugins/pacbox/pacbox_arm.S index 5b06af53c1..70d4a19724 100644 --- a/apps/plugins/pacbox/pacbox_arm.S +++ b/apps/plugins/pacbox/pacbox_arm.S @@ -25,6 +25,7 @@ .section .icode,"ax",%progbits .global blit_display + .type blit_display, %function /* void blit_display(fb_data* lcd_framebuffer, unsigned char* vbuf) diff --git a/apps/plugins/pdbox/PDa/intern/sfread~.c b/apps/plugins/pdbox/PDa/intern/sfread~.c index 0a369ce3f5..1da54e07cd 100644 --- a/apps/plugins/pdbox/PDa/intern/sfread~.c +++ b/apps/plugins/pdbox/PDa/intern/sfread~.c @@ -78,7 +78,7 @@ void sfread_open(t_sfread *x,t_symbol *filename) /* get the size */ #ifdef ROCKBOX - x->x_size = rb->filesize(x->x_fd); + x->x_size = rb->ffilesize(x->x_fd); #else fstat(x->x_fd,&fstate); x->x_size = fstate.st_size; diff --git a/apps/plugins/pdbox/pdbox.h b/apps/plugins/pdbox/pdbox.h index 2fd628fea2..e1144b5f22 100644 --- a/apps/plugins/pdbox/pdbox.h +++ b/apps/plugins/pdbox/pdbox.h @@ -306,7 +306,7 @@ enum pd_key_id #define PDPOD_WHEELLEFT BUTTON_UP #define PDPOD_WHEELRIGHT BUTTON_DOWN #define PDPOD_ACTION BUTTON_SELECT -#elif (CONFIG_KEYPAD == CTRU_PAD) +#elif (CONFIG_KEYPAD == N3DS_PAD) #define PDPOD_QUIT BUTTON_BACK #define PDPOD_PLAY BUTTON_USER #define PDPOD_PREVIOUS BUTTON_LEFT diff --git a/apps/plugins/pegbox.c b/apps/plugins/pegbox.c index 1074555399..3572320a3f 100644 --- a/apps/plugins/pegbox.c +++ b/apps/plugins/pegbox.c @@ -710,7 +710,7 @@ CONFIG_KEYPAD == MROBE500_PAD #define LVL_UP_TEXT "UP+MENU" #define LVL_DOWN_TEXT "DOWN+MENU" -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD /* use touchscreen */ #elif CONFIG_KEYPAD == RG_NANO_PAD @@ -730,7 +730,7 @@ CONFIG_KEYPAD == MROBE500_PAD #define LVL_UP_TEXT "R" #define LVL_DOWN_TEXT "L" -#elif CONFIG_KEYPAD == CTRU_PAD +#elif CONFIG_KEYPAD == N3DS_PAD #define PEGBOX_SELECT BUTTON_SELECT #define PEGBOX_QUIT BUTTON_BACK #define PEGBOX_RESTART BUTTON_MENU @@ -747,6 +747,23 @@ CONFIG_KEYPAD == MROBE500_PAD #define LVL_DOWN_TEXT "Start" #define SELECT_TEXT "A" +#elif CONFIG_KEYPAD == ECHO_R1_PAD +#define PEGBOX_SELECT BUTTON_A +#define PEGBOX_QUIT BUTTON_POWER +#define PEGBOX_RESTART BUTTON_X +#define PEGBOX_LVL_UP BUTTON_START +#define PEGBOX_LVL_DOWN BUTTON_SELECT +#define PEGBOX_UP BUTTON_UP +#define PEGBOX_DOWN BUTTON_DOWN +#define PEGBOX_RIGHT BUTTON_RIGHT +#define PEGBOX_LEFT BUTTON_LEFT + +#define QUIT_TEXT "Power" +#define RESTART_TEXT "X" +#define LVL_UP_TEXT "Start" +#define LVL_DOWN_TEXT "Select" +#define SELECT_TEXT "A" + #else #error "Unsupported keymap!" #endif diff --git a/apps/plugins/pictureflow/pictureflow.c b/apps/plugins/pictureflow/pictureflow.c index 872fbaa2ac..bba1a5d70a 100644 --- a/apps/plugins/pictureflow/pictureflow.c +++ b/apps/plugins/pictureflow/pictureflow.c @@ -1584,7 +1584,7 @@ static int load_album_index(void){ int album_idx, artist_idx; if (fr >= 0){ - const unsigned long filesize = rb->filesize(fr); + const unsigned long filesize = rb->ffilesize(fr); if (filesize > sizeof(data)) { if (rb->read(fr, &data, sizeof(data)) == sizeof(data) && diff --git a/apps/plugins/pitch_detector.c b/apps/plugins/pitch_detector.c index 556bac2841..a8c7944e22 100644 --- a/apps/plugins/pitch_detector.c +++ b/apps/plugins/pitch_detector.c @@ -354,7 +354,7 @@ static void load_settings(void) } /* basic consistency check */ - if(rb->filesize(fd) == sizeof(settings)){ + if(rb->ffilesize(fd) == sizeof(settings)){ rb->read(fd, &settings, sizeof(settings)); rb->memcpy(&hdd_settings, &settings, sizeof(settings)); } diff --git a/apps/plugins/pixel-painter.lua b/apps/plugins/pixel-painter.lua index f237cc1c25..f3e22a78d9 100644 --- a/apps/plugins/pixel-painter.lua +++ b/apps/plugins/pixel-painter.lua @@ -90,7 +90,7 @@ function calculate_par(board) return moves end --Manual garbage collection is needed so it doesn't eat into the - --audio buffer, see http://forums.rockbox.org/index.php/topic,27120.msg177434.html + --audio buffer, see https://www.rockbox.org/forums/index.php/topic,27120.msg177434.html collectgarbage("collect") moves = moves + 1 until false diff --git a/apps/plugins/plugin_crt0.c b/apps/plugins/plugin_crt0.c index a9270c4fa0..f64becec2e 100644 --- a/apps/plugins/plugin_crt0.c +++ b/apps/plugins/plugin_crt0.c @@ -24,7 +24,7 @@ PLUGIN_HEADER -#ifdef CTRU +#ifdef N3DS /* dummy undefined symbols to build plugins for ctru */ void __aeabi_unwind_cpp_pr0(void) {} struct _reent * _EXFUN(__getreent, (void)) { return NULL; } diff --git a/apps/plugins/pong.c b/apps/plugins/pong.c index e7c5e5671c..d044563cd7 100644 --- a/apps/plugins/pong.c +++ b/apps/plugins/pong.c @@ -317,7 +317,7 @@ CONFIG_KEYPAD == MROBE500_PAD #define PONG_RIGHT_UP BUTTON_BACK #define PONG_RIGHT_DOWN BUTTON_RIGHT -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD /* use touchscreen */ #elif CONFIG_KEYPAD == MA_PAD @@ -336,7 +336,7 @@ CONFIG_KEYPAD == MROBE500_PAD #define PONG_RIGHT_UP BUTTON_X #define PONG_RIGHT_DOWN BUTTON_Y -#elif CONFIG_KEYPAD == CTRU_PAD +#elif CONFIG_KEYPAD == N3DS_PAD #define PONG_QUIT BUTTON_BACK #define PONG_PAUSE BUTTON_SELECT #define PONG_LEFT_UP BUTTON_UP @@ -344,6 +344,14 @@ CONFIG_KEYPAD == MROBE500_PAD #define PONG_RIGHT_UP BUTTON_MENU #define PONG_RIGHT_DOWN BUTTON_POWER +#elif CONFIG_KEYPAD == ECHO_R1_PAD +#define PONG_QUIT BUTTON_POWER +#define PONG_PAUSE BUTTON_START +#define PONG_LEFT_UP BUTTON_Y +#define PONG_LEFT_DOWN BUTTON_LEFT +#define PONG_RIGHT_UP BUTTON_B +#define PONG_RIGHT_DOWN BUTTON_RIGHT + #else #error No keymap defined! #endif diff --git a/apps/plugins/reversi/reversi-gui.h b/apps/plugins/reversi/reversi-gui.h index b8883c7a50..cc8b305825 100644 --- a/apps/plugins/reversi/reversi-gui.h +++ b/apps/plugins/reversi/reversi-gui.h @@ -351,7 +351,7 @@ #define REVERSI_BUTTON_MAKE_MOVE BUTTON_SELECT #define REVERSI_BUTTON_MENU BUTTON_MENU -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD /* use touchscreen */ #elif (CONFIG_KEYPAD == MA_PAD) @@ -372,7 +372,7 @@ #define REVERSI_BUTTON_MAKE_MOVE BUTTON_A #define REVERSI_BUTTON_MENU BUTTON_B -#elif CONFIG_KEYPAD == CTRU_PAD +#elif CONFIG_KEYPAD == N3DS_PAD #define REVERSI_BUTTON_QUIT BUTTON_BACK #define REVERSI_BUTTON_UP BUTTON_UP #define REVERSI_BUTTON_DOWN BUTTON_DOWN @@ -381,6 +381,15 @@ #define REVERSI_BUTTON_MAKE_MOVE BUTTON_SELECT #define REVERSI_BUTTON_MENU BUTTON_MENU +#elif CONFIG_KEYPAD == ECHO_R1_PAD +#define REVERSI_BUTTON_QUIT BUTTON_POWER +#define REVERSI_BUTTON_UP BUTTON_UP +#define REVERSI_BUTTON_DOWN BUTTON_DOWN +#define REVERSI_BUTTON_LEFT BUTTON_LEFT +#define REVERSI_BUTTON_RIGHT BUTTON_RIGHT +#define REVERSI_BUTTON_MAKE_MOVE BUTTON_A +#define REVERSI_BUTTON_MENU BUTTON_START + #else #error No keymap defined! #endif diff --git a/apps/plugins/rockblox.c b/apps/plugins/rockblox.c index 306568b76a..e674ec3c03 100644 --- a/apps/plugins/rockblox.c +++ b/apps/plugins/rockblox.c @@ -457,7 +457,7 @@ #define ROCKBLOX_RESTART BUTTON_BACK #define ROCKBLOX_SCROLL_ENABLED 1 -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD /* use touchscreen */ #elif CONFIG_KEYPAD == MA_PAD @@ -482,7 +482,7 @@ #define ROCKBLOX_DROP BUTTON_A #define ROCKBLOX_RESTART BUTTON_B -#elif CONFIG_KEYPAD == CTRU_PAD +#elif CONFIG_KEYPAD == N3DS_PAD #define ROCKBLOX_OFF BUTTON_BACK #define ROCKBLOX_ROTATE_CCW BUTTON_POWER @@ -494,6 +494,19 @@ #define ROCKBLOX_DROP BUTTON_SELECT #define ROCKBLOX_RESTART BUTTON_USER +#elif CONFIG_KEYPAD == ECHO_R1_PAD + +#define ROCKBLOX_OFF BUTTON_POWER +#define ROCKBLOX_LEFT BUTTON_LEFT +#define ROCKBLOX_RIGHT BUTTON_RIGHT +#define ROCKBLOX_ROTATE_CCW BUTTON_UP +#define ROCKBLOX_ROTATE_CW BUTTON_DOWN +#define ROCKBLOX_ROTATE_CCW2 BUTTON_Y +#define ROCKBLOX_ROTATE_CW2 BUTTON_B +#define ROCKBLOX_DOWN BUTTON_X +#define ROCKBLOX_DROP BUTTON_A +#define ROCKBLOX_RESTART BUTTON_START + #else #error No keymap defined! #endif @@ -563,6 +576,23 @@ #define LEVEL_Y 142 #define LINES_Y 218 +#elif (LCD_WIDTH == 360) && (LCD_HEIGHT == 640) + +/* 10x20 board of 24px blocks = 240x480, leaving a 100px label column */ +#define BLOCK_WIDTH 24 +#define BLOCK_HEIGHT 24 +#define BOARD_X 8 +#define BOARD_Y 40 +#define LABEL_X 258 +#define SCORE_Y 60 +#define LEVEL_Y 140 +#define LINES_Y 220 +#define HIGH_LABEL_X 258 +#define HIGH_SCORE_Y 320 +#define HIGH_LEVEL_Y 400 +#define PREVIEW_X 258 +#define PREVIEW_Y 480 + #elif (LCD_WIDTH == 360) && (LCD_HEIGHT == 400) #define BLOCK_WIDTH 19 @@ -579,15 +609,17 @@ #define PREVIEW_X 258 #define PREVIEW_Y 300 -#elif (LCD_WIDTH == 320) && (LCD_HEIGHT == 240) +#elif ((LCD_WIDTH == 320) || (LCD_WIDTH == 400)) && (LCD_HEIGHT == 240) +/* 400x240: the 320x240 layout, centred */ +#define LAYOUT_X ((LCD_WIDTH - 320) / 2) #define BLOCK_WIDTH 12 #define BLOCK_HEIGHT 12 -#define BOARD_X 86 +#define BOARD_X (LAYOUT_X + 86) #define BOARD_Y 0 -#define PREVIEW_X 12 +#define PREVIEW_X (LAYOUT_X + 12) #define PREVIEW_Y 11 -#define LABEL_X 242 +#define LABEL_X (LAYOUT_X + 242) #define SCORE_Y 25 #define LEVEL_Y 70 #define LINES_Y 105 @@ -805,6 +837,20 @@ extern const fb_data rockblox_background[]; +#ifndef LAYOUT_X +#define LAYOUT_X 0 +#endif + +/* the background, which spans the layout - not the screen, when the + * layout is a smaller one centred */ +static void draw_background(void) +{ + if (LAYOUT_X > 0) + rb->lcd_clear_display(); + rb->lcd_bitmap(rockblox_background, LAYOUT_X, 0, + LCD_WIDTH - 2 * LAYOUT_X, LCD_HEIGHT); +} + #ifndef HIGHSCORE_SPACE #define HIGHSCORE_SPACE " " #endif @@ -1085,7 +1131,7 @@ static void init_rockblox (bool resume) tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); - rb->lcd_bitmap (rockblox_background, 0, 0, LCD_WIDTH, LCD_HEIGHT); + draw_background(); if (!resume) { rockblox_status.level = 1; @@ -1553,7 +1599,7 @@ static int rockblox_loop (void) backlight_ignore_timeout(); /* get rid of the splash text */ - rb->lcd_bitmap (rockblox_background, 0, 0, LCD_WIDTH, LCD_HEIGHT); + draw_background(); show_details (); #ifdef HIGH_SCORE_Y show_highscores (); diff --git a/apps/plugins/rockboy/rockboy.c b/apps/plugins/rockboy/rockboy.c index 16889108b9..fb822996e3 100644 --- a/apps/plugins/rockboy/rockboy.c +++ b/apps/plugins/rockboy/rockboy.c @@ -74,7 +74,7 @@ static void setoptions (void) int optionssize = sizeof(options); int filesize = 0; if(fd >= 0) - filesize = rb->filesize(fd); + filesize = rb->ffilesize(fd); /* don't read the option file if the size * is not as expected to avoid crash */ @@ -457,7 +457,7 @@ static void setoptions (void) options.SELECT = BUTTON_VOL_UP; options.MENU = BUTTON_POWER; -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD /* use touchscreen */ #elif CONFIG_KEYPAD == MA_PAD @@ -482,7 +482,7 @@ static void setoptions (void) options.SELECT = BUTTON_FN; options.MENU = BUTTON_X; -#elif CONFIG_KEYPAD == CTRU_PAD +#elif CONFIG_KEYPAD == N3DS_PAD options.UP = BUTTON_UP; options.DOWN = BUTTON_DOWN; options.LEFT = BUTTON_LEFT; @@ -494,11 +494,23 @@ static void setoptions (void) options.SELECT = BUTTON_USER; options.MENU = BUTTON_MENU; +#elif CONFIG_KEYPAD == ECHO_R1_PAD + options.UP = BUTTON_UP; + options.DOWN = BUTTON_DOWN; + options.LEFT = BUTTON_LEFT; + options.RIGHT = BUTTON_RIGHT; + + options.A = BUTTON_A; + options.B = BUTTON_B; + options.START = BUTTON_START; + options.SELECT = BUTTON_SELECT; + options.MENU = BUTTON_POWER; + #else #error No Keymap Defined! #endif -#if defined(HAVE_TOUCHSCREEN) && (CONFIG_KEYPAD != CTRU_PAD) +#if defined(HAVE_TOUCHSCREEN) && (CONFIG_KEYPAD != N3DS_PAD) options.UP = BUTTON_TOPMIDDLE; options.DOWN = BUTTON_BOTTOMMIDDLE; options.START = BUTTON_TOPRIGHT; diff --git a/apps/plugins/rockpaint.c b/apps/plugins/rockpaint.c index 6fdedb0736..b0731afb38 100644 --- a/apps/plugins/rockpaint.c +++ b/apps/plugins/rockpaint.c @@ -392,7 +392,7 @@ #define ROCKPAINT_LEFT BUTTON_LEFT #define ROCKPAINT_RIGHT BUTTON_RIGHT -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD /* use touchscreen */ #elif CONFIG_KEYPAD == EROSQ_PAD @@ -428,7 +428,7 @@ #define ROCKPAINT_LEFT BUTTON_LEFT #define ROCKPAINT_RIGHT BUTTON_RIGHT -#elif CONFIG_KEYPAD == CTRU_PAD +#elif CONFIG_KEYPAD == N3DS_PAD #define ROCKPAINT_QUIT BUTTON_BACK #define ROCKPAINT_DRAW BUTTON_SELECT #define ROCKPAINT_MENU BUTTON_MENU @@ -439,6 +439,17 @@ #define ROCKPAINT_LEFT BUTTON_LEFT #define ROCKPAINT_RIGHT BUTTON_RIGHT +#elif CONFIG_KEYPAD == ECHO_R1_PAD +#define ROCKPAINT_QUIT BUTTON_POWER +#define ROCKPAINT_DRAW BUTTON_A +#define ROCKPAINT_MENU BUTTON_B +#define ROCKPAINT_TOOLBAR BUTTON_X +#define ROCKPAINT_TOOLBAR2 BUTTON_Y +#define ROCKPAINT_UP BUTTON_UP +#define ROCKPAINT_DOWN BUTTON_DOWN +#define ROCKPAINT_LEFT BUTTON_LEFT +#define ROCKPAINT_RIGHT BUTTON_RIGHT + #else #error "Please define keys for this keypad" #endif diff --git a/apps/plugins/sdl/sdl.make b/apps/plugins/sdl/sdl.make index 8b27c49341..325b430028 100644 --- a/apps/plugins/sdl/sdl.make +++ b/apps/plugins/sdl/sdl.make @@ -38,8 +38,8 @@ SDLFLAGS = -I$(SDL_SRCDIR)/include $(filter-out -O%,$(PLUGINFLAGS)) \ #-ffast-math -funroll-loops -fomit-frame-pointer -fexpensive-optimizations \ #-D_GNU_SOURCE=1 -D_REENTRANT -DSDL -DELF -# CTRU does need these to avoid compiler errors -ifeq ($(APP_TYPE),ctru-app) +# N3DS does need these to avoid compiler errors +ifeq ($(APP_TYPE),3ds-homebrew) SDLFLAGS += -Wno-int-conversion -Wno-incompatible-pointer-types \ -Wno-implicit-function-declaration -Wno-implicit-int endif diff --git a/apps/plugins/sliding_puzzle.c b/apps/plugins/sliding_puzzle.c index d3c223e2ec..b3a940bfda 100644 --- a/apps/plugins/sliding_puzzle.c +++ b/apps/plugins/sliding_puzzle.c @@ -347,7 +347,7 @@ CONFIG_KEYPAD == MROBE500_PAD #define PUZZLE_SHUFFLE BUTTON_BACK #define PUZZLE_PICTURE BUTTON_PLAY -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD /* use touchscreen */ #elif CONFIG_KEYPAD == MA_PAD @@ -368,7 +368,7 @@ CONFIG_KEYPAD == MROBE500_PAD #define PUZZLE_SHUFFLE BUTTON_B #define PUZZLE_PICTURE BUTTON_A -#elif CONFIG_KEYPAD == CTRU_PAD +#elif CONFIG_KEYPAD == N3DS_PAD #define PUZZLE_QUIT BUTTON_BACK #define PUZZLE_LEFT BUTTON_LEFT #define PUZZLE_RIGHT BUTTON_RIGHT @@ -377,6 +377,15 @@ CONFIG_KEYPAD == MROBE500_PAD #define PUZZLE_SHUFFLE BUTTON_SELECT #define PUZZLE_PICTURE BUTTON_MENU +#elif CONFIG_KEYPAD == ECHO_R1_PAD +#define PUZZLE_QUIT BUTTON_POWER +#define PUZZLE_LEFT BUTTON_LEFT +#define PUZZLE_RIGHT BUTTON_RIGHT +#define PUZZLE_UP BUTTON_UP +#define PUZZLE_DOWN BUTTON_DOWN +#define PUZZLE_SHUFFLE BUTTON_SELECT +#define PUZZLE_PICTURE BUTTON_START + #else #error No keymap defined! #endif diff --git a/apps/plugins/snake.c b/apps/plugins/snake.c index a2213cf0b5..3eb08271cb 100644 --- a/apps/plugins/snake.c +++ b/apps/plugins/snake.c @@ -304,7 +304,7 @@ dir is the current direction of the snake - 0=up, 1=right, 2=down, 3=left; #define SNAKE_DOWN BUTTON_DOWN #define SNAKE_PLAYPAUSE BUTTON_PLAY -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD /* use touchscreen */ #elif CONFIG_KEYPAD == MA_PAD @@ -323,7 +323,7 @@ dir is the current direction of the snake - 0=up, 1=right, 2=down, 3=left; #define SNAKE_DOWN BUTTON_DOWN #define SNAKE_PLAYPAUSE BUTTON_A -#elif CONFIG_KEYPAD == CTRU_PAD +#elif CONFIG_KEYPAD == N3DS_PAD #define SNAKE_QUIT BUTTON_BACK #define SNAKE_LEFT BUTTON_LEFT #define SNAKE_RIGHT BUTTON_RIGHT @@ -331,6 +331,14 @@ dir is the current direction of the snake - 0=up, 1=right, 2=down, 3=left; #define SNAKE_DOWN BUTTON_DOWN #define SNAKE_PLAYPAUSE BUTTON_SELECT +#elif CONFIG_KEYPAD == ECHO_R1_PAD +#define SNAKE_QUIT BUTTON_POWER +#define SNAKE_LEFT BUTTON_LEFT +#define SNAKE_RIGHT BUTTON_RIGHT +#define SNAKE_UP BUTTON_UP +#define SNAKE_DOWN BUTTON_DOWN +#define SNAKE_PLAYPAUSE BUTTON_START + #else #error No keymap defined! #endif diff --git a/apps/plugins/snake2.c b/apps/plugins/snake2.c index 9a269b48be..cc8eb06e35 100644 --- a/apps/plugins/snake2.c +++ b/apps/plugins/snake2.c @@ -448,7 +448,7 @@ CONFIG_KEYPAD == MROBE500_PAD #define SNAKE2_PLAYPAUSE BUTTON_PLAY #define SNAKE2_PLAYPAUSE_TEXT "PLAY" -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD /* use touchscreen */ #elif CONFIG_KEYPAD == MA_PAD @@ -469,7 +469,7 @@ CONFIG_KEYPAD == MROBE500_PAD #define SNAKE2_PLAYPAUSE BUTTON_A #define SNAKE2_PLAYPAUSE_TEXT "A" -#elif CONFIG_KEYPAD == CTRU_PAD +#elif CONFIG_KEYPAD == N3DS_PAD #define SNAKE2_LEFT BUTTON_LEFT #define SNAKE2_RIGHT BUTTON_RIGHT #define SNAKE2_UP BUTTON_UP @@ -478,6 +478,15 @@ CONFIG_KEYPAD == MROBE500_PAD #define SNAKE2_PLAYPAUSE BUTTON_SELECT #define SNAKE2_PLAYPAUSE_TEXT "A" +#elif CONFIG_KEYPAD == ECHO_R1_PAD +#define SNAKE2_LEFT BUTTON_LEFT +#define SNAKE2_RIGHT BUTTON_RIGHT +#define SNAKE2_UP BUTTON_UP +#define SNAKE2_DOWN BUTTON_DOWN +#define SNAKE2_QUIT BUTTON_POWER +#define SNAKE2_PLAYPAUSE BUTTON_START +#define SNAKE2_PLAYPAUSE_TEXT "START" + #else #error No keymap defined! #endif diff --git a/apps/plugins/sokoban.c b/apps/plugins/sokoban.c index ae5ccf1574..5a539fd9ef 100644 --- a/apps/plugins/sokoban.c +++ b/apps/plugins/sokoban.c @@ -680,7 +680,7 @@ #define BUTTON_SAVE BUTTON_BACK #define BUTTON_SAVE_NAME "BACK" -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD /* use touchscreen */ #elif CONFIG_KEYPAD == MA_PAD @@ -712,7 +712,7 @@ #define BUTTON_SAVE BUTTON_A #define BUTTON_SAVE_NAME "A" -#elif CONFIG_KEYPAD == CTRU_PAD +#elif CONFIG_KEYPAD == N3DS_PAD #define SOKOBAN_LEFT BUTTON_LEFT #define SOKOBAN_RIGHT BUTTON_RIGHT #define SOKOBAN_UP BUTTON_UP @@ -733,6 +733,19 @@ #define SOKOBAN_LEVEL_REPEAT_NAME "[TOPRIGHT]" #define BUTTON_SAVE_NAME "A [CENTER]" +#elif CONFIG_KEYPAD == ECHO_R1_PAD +#define SOKOBAN_LEFT BUTTON_LEFT +#define SOKOBAN_RIGHT BUTTON_RIGHT +#define SOKOBAN_UP BUTTON_UP +#define SOKOBAN_DOWN BUTTON_DOWN +#define SOKOBAN_MENU BUTTON_POWER +#define SOKOBAN_UNDO BUTTON_X +#define SOKOBAN_REDO BUTTON_Y +#define SOKOBAN_LEVEL_REPEAT BUTTON_SELECT +#define SOKOBAN_PAUSE BUTTON_START +#define BUTTON_SAVE BUTTON_A +#define BUTTON_SAVE_NAME "A" + #else #error No keymap defined! #endif diff --git a/apps/plugins/solitaire.c b/apps/plugins/solitaire.c index 215412164a..17e24cbc54 100644 --- a/apps/plugins/solitaire.c +++ b/apps/plugins/solitaire.c @@ -722,7 +722,7 @@ CONFIG_KEYPAD == MROBE500_PAD # define HK_CUR2STACK "HOLD SELECT" # define HK_REM2STACK "VOL+" -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD # define SOL_QUIT BUTTON_POWER #elif CONFIG_KEYPAD == RG_NANO_PAD @@ -742,7 +742,7 @@ CONFIG_KEYPAD == MROBE500_PAD # define HK_CUR2STACK "B" # define HK_REM2STACK "R" -#elif (CONFIG_KEYPAD == CTRU_PAD) +#elif (CONFIG_KEYPAD == N3DS_PAD) # define SOL_QUIT BUTTON_BACK # define SOL_UP BUTTON_UP # define SOL_DOWN BUTTON_DOWN @@ -780,6 +780,24 @@ CONFIG_KEYPAD == MROBE500_PAD # define HK_REM2STACK "MENU+UP" +#elif (CONFIG_KEYPAD == ECHO_R1_PAD) +# define SOL_QUIT BUTTON_POWER +# define SOL_UP BUTTON_UP +# define SOL_DOWN BUTTON_DOWN +# define SOL_LEFT BUTTON_LEFT +# define SOL_RIGHT BUTTON_RIGHT +# define SOL_MOVE_PRE BUTTON_A +# define SOL_MOVE (BUTTON_A | BUTTON_REL) +# define SOL_DRAW BUTTON_X +# define SOL_REM2CUR (BUTTON_Y | BUTTON_REPEAT) +# define SOL_CUR2STACK (BUTTON_A | BUTTON_REPEAT) +# define SOL_REM2STACK BUTTON_START +# define HK_MOVE "A" +# define HK_DRAW "X" +# define HK_REM2CUR "Long Y" +# define HK_CUR2STACK "Long A.." +# define HK_REM2STACK "Start" + #else #error No keymap defined! #endif diff --git a/apps/plugins/spacerocks.c b/apps/plugins/spacerocks.c index 1555c2d4ad..56bae2125b 100644 --- a/apps/plugins/spacerocks.c +++ b/apps/plugins/spacerocks.c @@ -362,7 +362,7 @@ #define AST_RIGHT BUTTON_RIGHT #define AST_FIRE BUTTON_PLAY -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD /* use touchscreen */ #elif CONFIG_KEYPAD == MA_PAD @@ -383,7 +383,7 @@ #define AST_RIGHT BUTTON_RIGHT #define AST_FIRE BUTTON_A -#elif CONFIG_KEYPAD == CTRU_PAD +#elif CONFIG_KEYPAD == N3DS_PAD #define AST_PAUSE BUTTON_MENU #define AST_QUIT BUTTON_BACK #define AST_THRUST BUTTON_UP @@ -392,6 +392,16 @@ #define AST_RIGHT BUTTON_RIGHT #define AST_FIRE BUTTON_SELECT +#elif CONFIG_KEYPAD == ECHO_R1_PAD + +#define AST_PAUSE BUTTON_START +#define AST_QUIT BUTTON_POWER +#define AST_THRUST BUTTON_UP +#define AST_HYPERSPACE BUTTON_DOWN +#define AST_LEFT BUTTON_LEFT +#define AST_RIGHT BUTTON_RIGHT +#define AST_FIRE BUTTON_A + #else #error No keymap defined! #endif diff --git a/apps/plugins/star.c b/apps/plugins/star.c index 14c4d671e2..92b56447d2 100644 --- a/apps/plugins/star.c +++ b/apps/plugins/star.c @@ -650,7 +650,7 @@ #define STAR_LEVEL_DOWN_NAME "VOL-" #define STAR_LEVEL_REPEAT_NAME "BACK" -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD #define STAR_QUIT BUTTON_POWER #define STAR_QUIT_NAME "POWER" @@ -687,7 +687,7 @@ #define STAR_LEVEL_DOWN_NAME "L" #define STAR_LEVEL_REPEAT_NAME "B" -#elif CONFIG_KEYPAD == CTRU_PAD +#elif CONFIG_KEYPAD == N3DS_PAD #define STAR_QUIT BUTTON_BACK #define STAR_LEFT BUTTON_LEFT @@ -704,6 +704,23 @@ #define STAR_LEVEL_DOWN_NAME "Start" #define STAR_LEVEL_REPEAT_NAME "Y" +#elif CONFIG_KEYPAD == ECHO_R1_PAD + +#define STAR_QUIT BUTTON_POWER +#define STAR_LEFT BUTTON_LEFT +#define STAR_RIGHT BUTTON_RIGHT +#define STAR_UP BUTTON_UP +#define STAR_DOWN BUTTON_DOWN +#define STAR_TOGGLE_CONTROL BUTTON_A +#define STAR_LEVEL_UP BUTTON_START +#define STAR_LEVEL_DOWN BUTTON_SELECT +#define STAR_LEVEL_REPEAT BUTTON_X +#define STAR_TOGGLE_CONTROL_NAME "A" +#define STAR_QUIT_NAME "Power" +#define STAR_LEVEL_UP_NAME "Start" +#define STAR_LEVEL_DOWN_NAME "Select" +#define STAR_LEVEL_REPEAT_NAME "X" + #else #error No keymap defined! #endif diff --git a/apps/plugins/stopwatch.c b/apps/plugins/stopwatch.c index c0a6b2dc01..87b1857921 100644 --- a/apps/plugins/stopwatch.c +++ b/apps/plugins/stopwatch.c @@ -305,7 +305,7 @@ #define STOPWATCH_SCROLL_UP BUTTON_UP #define STOPWATCH_SCROLL_DOWN BUTTON_DOWN -#elif CONFIG_KEYPAD == CTRU_PAD +#elif CONFIG_KEYPAD == N3DS_PAD #define STOPWATCH_QUIT BUTTON_BACK #define STOPWATCH_START_STOP BUTTON_SELECT #define STOPWATCH_RESET_TIMER BUTTON_MENU @@ -313,6 +313,14 @@ #define STOPWATCH_SCROLL_UP BUTTON_UP #define STOPWATCH_SCROLL_DOWN BUTTON_DOWN +#elif CONFIG_KEYPAD == ECHO_R1_PAD +#define STOPWATCH_QUIT BUTTON_POWER +#define STOPWATCH_START_STOP BUTTON_START +#define STOPWATCH_RESET_TIMER BUTTON_SELECT +#define STOPWATCH_LAP_TIMER BUTTON_RIGHT +#define STOPWATCH_SCROLL_UP BUTTON_UP +#define STOPWATCH_SCROLL_DOWN BUTTON_DOWN + #else #error No keymap defined! #endif diff --git a/apps/plugins/sudoku/sudoku.c b/apps/plugins/sudoku/sudoku.c index 3f192e9f0c..22e1ac50f6 100644 --- a/apps/plugins/sudoku/sudoku.c +++ b/apps/plugins/sudoku/sudoku.c @@ -146,8 +146,9 @@ static const char default_game[9][9] = #define MARK_SPACE 1 /* Pixels between two marks */ #define MARK_SIZE 4 /* Mark width and height */ -#elif (LCD_HEIGHT==240) && (LCD_WIDTH==320) +#elif (LCD_HEIGHT==240) && ((LCD_WIDTH==320) || (LCD_WIDTH==400)) /* iPod Video - 320x240, 9 cells @ 24x24 with 14 border lines */ +/* Samsung YP-CP3 - 400x240, the same, centred */ #define MARK_OFFS 1 /* Pixels between border and mark */ #define MARK_SPACE 2 /* Pixels between two marks */ #define MARK_SIZE 6 /* Mark width and height */ diff --git a/apps/plugins/sudoku/sudoku.h b/apps/plugins/sudoku/sudoku.h index fe2bf6c592..410d15acf0 100644 --- a/apps/plugins/sudoku/sudoku.h +++ b/apps/plugins/sudoku/sudoku.h @@ -458,7 +458,7 @@ #define SUDOKU_BUTTON_TOGGLE BUTTON_PLAY #define SUDOKU_BUTTON_POSSIBLE BUTTON_BACK -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD /* use touchscreen */ #elif CONFIG_KEYPAD == RG_NANO_PAD @@ -472,7 +472,7 @@ #define SUDOKU_BUTTON_MENU BUTTON_B #define SUDOKU_BUTTON_POSSIBLE BUTTON_X -#elif CONFIG_KEYPAD == CTRU_PAD +#elif CONFIG_KEYPAD == N3DS_PAD #define SUDOKU_BUTTON_QUIT BUTTON_BACK #define SUDOKU_BUTTON_UP BUTTON_UP #define SUDOKU_BUTTON_DOWN BUTTON_DOWN @@ -482,6 +482,16 @@ #define SUDOKU_BUTTON_TOGGLE BUTTON_SELECT #define SUDOKU_BUTTON_POSSIBLE BUTTON_USER +#elif CONFIG_KEYPAD == ECHO_R1_PAD +#define SUDOKU_BUTTON_QUIT BUTTON_POWER +#define SUDOKU_BUTTON_UP BUTTON_UP +#define SUDOKU_BUTTON_DOWN BUTTON_DOWN +#define SUDOKU_BUTTON_LEFT BUTTON_LEFT +#define SUDOKU_BUTTON_RIGHT BUTTON_RIGHT +#define SUDOKU_BUTTON_MENU BUTTON_START +#define SUDOKU_BUTTON_TOGGLE BUTTON_A +#define SUDOKU_BUTTON_POSSIBLE BUTTON_B + #else #error No keymap defined! #endif diff --git a/apps/plugins/superdom.c b/apps/plugins/superdom.c index 03d6c039eb..46ba5b1d15 100644 --- a/apps/plugins/superdom.c +++ b/apps/plugins/superdom.c @@ -94,7 +94,13 @@ char buf[255]; #define MY_BITMAP_PART rb->lcd_mono_bitmap_part #endif -#if LCD_WIDTH > LCD_HEIGHT +#if (LCD_WIDTH == 400) && (LCD_HEIGHT == 240) +/* boxes as wide as the screen allows make a board taller than it: take + * the 320x240 ones, which its board items are drawn for */ +#define BOX_WIDTH ((320-(MARGIN*2))/BOARD_SIZE) +#define BOX_HEIGHT ((BOX_WIDTH*2)/3) + +#elif LCD_WIDTH > LCD_HEIGHT #define BOX_WIDTH ((LCD_WIDTH-(MARGIN*2))/BOARD_SIZE) #define BOX_HEIGHT ((BOX_WIDTH*2)/3) diff --git a/apps/plugins/tagcache/tagcache.c b/apps/plugins/tagcache/tagcache.c index 58d263da7b..602e366793 100644 --- a/apps/plugins/tagcache/tagcache.c +++ b/apps/plugins/tagcache/tagcache.c @@ -54,7 +54,7 @@ static bool logdump(bool append); #define vsnprintf rb->vsnprintf #define mkdir rb->mkdir -#define filesize rb->filesize +#define ffilesize rb->ffilesize #define strtok_r rb->strtok_r #define strncasecmp rb->strncasecmp diff --git a/apps/plugins/test_codec.c b/apps/plugins/test_codec.c index ba5b264372..7e1ab3304f 100644 --- a/apps/plugins/test_codec.c +++ b/apps/plugins/test_codec.c @@ -21,6 +21,7 @@ #include "lib/pluginlib_touchscreen.h" #include "lib/pluginlib_exit.h" #include "lib/pluginlib_actions.h" +#include "lib/xlcd.h" /* this set the context to use with PLA */ static const struct button_mapping *plugin_contexts[] = { pla_main_ctx }; @@ -41,27 +42,50 @@ static const struct opt_items boost_settings[2] = { /* Log functions copied from test_disk.c */ static int line = 0; static int max_line = 0; +static int line_height = 0; static int log_fd = -1; +static char logfilename[MAX_PATH]; static void log_close(void) { if (log_fd >= 0) rb->close(log_fd); + log_fd = -1; +} + +/* The file's length only reaches the disk when it is closed. So that what + has been logged survives the player being switched off, or its battery + running out, before the plugin is left: close the log and open it again + to carry on. */ +static void log_commit(void) +{ + if (log_fd >= 0) + { + rb->close(log_fd); + log_fd = rb->open(logfilename, O_WRONLY|O_APPEND); + } } static bool log_init(bool use_logfile) { - int h; - char logfilename[MAX_PATH]; + static bool close_at_exit = false; - rb->lcd_getstringsize("A", NULL, &h); - max_line = LCD_HEIGHT / h; + rb->lcd_getstringsize("A", NULL, &line_height); + max_line = LCD_HEIGHT / line_height; line = 0; rb->lcd_clear_display(); rb->lcd_update(); + log_close(); + if (use_logfile) { - log_close(); + /* Leaving on USB or power off goes through exit(), not the end of + plugin_start(). */ + if (!close_at_exit) + { + atexit(log_close); + close_at_exit = true; + } rb->create_numbered_filename(logfilename, HOME_DIR, "test_codec_log_", ".txt", 2 IF_CNFN_NUM_(, NULL)); log_fd = rb->open(logfilename, O_RDWR|O_CREAT|O_TRUNC, 0666); @@ -73,12 +97,17 @@ static bool log_init(bool use_logfile) static void log_text(char *text, bool advance) { + if (line >= max_line) + { + /* Screen is full - scroll up to make room for this line */ + xlcd_scroll_up(line_height); + line = max_line - 1; + } rb->lcd_puts(0, line, text); rb->lcd_update(); if (advance) { - if (++line >= max_line) - line = 0; + line++; if (log_fd >= 0) rb->fdprintf(log_fd, "%s\n", text); } @@ -117,6 +146,10 @@ static bool checksum; static uint32_t crc32; static volatile unsigned int elapsed; +/* Codecs such as SID never end on their own, so limit how long they run */ +#define SID_MAX_SECONDS 120 +static unsigned long codec_frequency; /* Codec output rate before DSP */ +static unsigned long decoded_samples; /* Samples output by the codec */ static volatile bool codec_playing; static volatile long codec_action; static volatile long endtick; @@ -181,7 +214,7 @@ void init_wav(char* filename) void close_wav(void) { - int filesize = rb->filesize(wavinfo.fd); + int filesize = rb->ffilesize(wavinfo.fd); int channels = (wavinfo.stereomode == STEREO_MONO) ? 1 : 2; int bps = 16; /* TODO */ @@ -245,6 +278,8 @@ static int process_dsp(const void *ch1, const void *ch2, int count) /* Null output */ static void pcmbuf_insert_null(const void *ch1, const void *ch2, int count) { + decoded_samples += count; + if (use_dsp) process_dsp(ch1, ch2, count); @@ -294,6 +329,8 @@ static int fill_buffer(int new_offset){ /* WAV output or calculate crc32 of output*/ static void pcmbuf_insert_wav_checksum(const void *ch1, const void *ch2, int count) { + decoded_samples += count; + /* Prevent idle poweroff */ rb->reset_poweroff_timer(); @@ -477,6 +514,15 @@ static void seek_complete(void) static long get_command(intptr_t *param) { rb->yield(); + + /* SID files play forever, so stop them after a fixed time */ + if (track.id3.codectype == AFMT_SID && codec_frequency >= 1000) + { + elapsed = decoded_samples / (codec_frequency / 1000); + if (decoded_samples >= SID_MAX_SECONDS * codec_frequency) + return CODEC_ACTION_HALT; + } + return codec_action; (void)param; } @@ -502,6 +548,7 @@ static void configure(int setting, intptr_t value) { case DSP_SET_FREQUENCY: DEBUGF("samplerate=%d\n",(int)value); + codec_frequency = value; if (use_dsp) { wavinfo.samplerate = rb->dsp_configure( ci.dsp, DSP_GET_OUT_FREQUENCY, 0); @@ -658,7 +705,7 @@ static enum plugin_status test_track(const char* filename) goto exit; } - track.filesize = rb->filesize(fd); + track.filesize = rb->ffilesize(fd); /* Clear the id3 struct */ rb->memset(&track.id3, 0, sizeof(struct mp3entry)); @@ -669,6 +716,11 @@ static enum plugin_status test_track(const char* filename) goto exit; } + /* SID length is the subsong count, use the decode limit instead so the + benchmark results are meaningful */ + if (track.id3.codectype == AFMT_SID) + track.id3.length = SID_MAX_SECONDS * 1000; + if (track.filesize > audiosize) { audiobufsize=audiosize; @@ -697,6 +749,11 @@ static enum plugin_status test_track(const char* filename) if (use_dsp) { rb->dsp_configure(ci.dsp, DSP_RESET, 0); + /* The DSP is told its output samplerate when a track is played. + * Tell it here as well, or it is that of the last track and not + * what playback would use now. */ + rb->dsp_configure(ci.dsp, DSP_SET_OUT_FREQUENCY, + rb->mixer_get_frequency()); rb->dsp_configure(ci.dsp, DSP_FLUSH, 0); } @@ -708,6 +765,8 @@ static enum plugin_status test_track(const char* filename) codec_playing = true; codec_action = CODEC_ACTION_NULL; + codec_frequency = 0; + decoded_samples = 0; rb->codec_thread_do_callback(codec_thread, NULL); @@ -829,7 +888,11 @@ void plugin_quit(void) else #endif do { - btn = pluginlib_getaction(TIMEOUT_BLOCK, plugin_contexts, + /* lcd_update() does nothing while the LCD is off, and the + backlight_on() request is asynchronous, so the results may + not have reached the display yet - keep refreshing it */ + rb->lcd_update(); + btn = pluginlib_getaction(HZ/2, plugin_contexts, ARRAYLEN(plugin_contexts)); exit_on_usb(btn); } while ((codec_action != CODEC_ACTION_HALT) @@ -885,6 +948,8 @@ enum plugin_status plugin_start(const void* parameter) WRITE_WAV_WITH_DSP, CHECKSUM, CHECKSUM_DIR, + CHECKSUM_WITH_DSP, + CHECKSUM_DIR_WITH_DSP, QUIT, #ifdef HAVE_ADJUSTABLE_CPU_FREQ BOOST, @@ -901,6 +966,8 @@ enum plugin_status plugin_start(const void* parameter) "Write WAV with DSP", "Checksum", "Checksum folder", + "Checksum with DSP", + "Checksum folder with DSP", "Quit", #ifdef HAVE_ADJUSTABLE_CPU_FREQ "Boosting", @@ -934,6 +1001,13 @@ menu: scandir = 0; + /* Map checksum runs with DSP to CHECKSUM and CHECKSUM_DIR: the + * checksum is then that of the DSP's output. */ + bool dsp_checksum = (result == CHECKSUM_WITH_DSP || + result == CHECKSUM_DIR_WITH_DSP); + if (dsp_checksum) + result -= 2; + /* Map test runs with checksum calcualtion to standard runs * SPEED_TEST and SPEED_TEST_DIR and set the 'checksum' flag. */ if ((checksum = (result == CHECKSUM || @@ -945,6 +1019,8 @@ menu: if ((use_dsp = (result >= SPEED_TEST_WITH_DSP && result <= WRITE_WAV_WITH_DSP))) result -= 3; + else + use_dsp = dsp_checksum; if (result == SPEED_TEST) { wavinfo.fd = -1; @@ -1004,6 +1080,7 @@ menu: break; log_text("", true); + log_commit(); } /* Read next entry */ @@ -1012,6 +1089,9 @@ menu: rb->closedir(dir); } + + /* The run is over: nothing more goes in the log. */ + log_close(); } else { /* Just test the file */ res = test_track(parameter); diff --git a/apps/plugins/test_cyc.c b/apps/plugins/test_cyc.c new file mode 100644 index 0000000000..6264b8b3a8 --- /dev/null +++ b/apps/plugins/test_cyc.c @@ -0,0 +1,2299 @@ +/*************************************************************************** +* __________ __ ___. +* Open \______ \ ____ ____ | | _\_ |__ _______ ___ +* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / +* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < +* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ +* \/ \/ \/ \/ \/ +* $Id$ +* +* Copyright (C) 2026 Michael Giacomelli +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 2 +* of the License, or (at your option) any later version. +* +* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +* KIND, either express or implied. +* +****************************************************************************/ + +/* Measures what instructions cost on the running device, in CPU cycles. + * + * Each benchmark is sixteen copies of one instruction and the loop control. + * Where plugins get IRAM it is timed with the code in IRAM and in DRAM, and + * with the data it touches in IRAM and in DRAM (a 256-byte buffer, so the + * DRAM copy stays cached). The empty loop is timed the same way and + * subtracted, so every figure is cycles per instruction. Targets without + * plugin IRAM print one column. + * + * Stores are also timed with ALU instructions between them, whose cost is + * subtracted, to tell a cost of back-to-back stores from one of code and + * data sharing a memory. + * + * Cache misses are priced two ways over the plugin buffer. A pointer chase, + * where every load's address is the value the previous load returned, gives + * the full latency of a miss with nothing overlapping it. Streams of + * independent loads, a word, a 32-byte line and a four-word ldm at a time, + * give what a miss costs when it is free to overlap the loads around it. + * + * Instructions whose condition fails are timed too (the loop leaves the + * carry set, so "cc" never executes), and so are stores to the stack, on + * this thread and again on the codec thread, whose stack a codec uses and + * which may not be in the memory the main thread's is. + * + * Where plugins have the room, a store from IRAM code is also timed at + * addresses 4 KB apart across IRAM, since that extra cycle depends on + * which part of IRAM the data is in. + * + * Three more cache costs are timed: a store to a line that is not cached, + * loads that miss far apart rather than next to each other, and an + * instruction fetch miss, from a chain of branches through more DRAM code + * than the cache holds. + * + * ARMv5 adds the DSP multiplies, clz, qadd, ldrd/strd and pld; ARMv6 the + * top-word and dual 16-bit multiplies, umaal, ssat, rev, the extends, pkhbt, + * the SIMD adds and multiply result latency. Each set is behind #if + * ARM_ARCH so the file builds for every ARM target. + * + * Runs are timed with the finest free-running timer the SoC has, or failing + * that with the tick, stretching each run to a hundred ticks, which keeps + * the figures to about a percent. + * + * Results go to the screen and to /test_cyc.txt. + */ + +#include "plugin.h" + +/* Rows that have been run and told nothing the rows left in do not, or + that were a step to a finding since made another way. They are kept, + each group with a line on what it showed, and 1 here runs them again: + + - Instruction rows that came out as the sum of their parts on an + ARM926: a return through lr and bx, a conditional return, conditional + instructions that execute, flag-setting, a register shift in an add, + ldm of 3, the third register of an ldm used next, an ldm feeding a + multiply, writeback, stm then ldm, push and pop of ten, mul then use + (as mla then use), a load feeding a register shift. + - Multiplies with 24-bit and negative Rm, and mul, smull and smlal by + operand size: the same whatever the operands, as the rows kept show. + - A miss and then the rest of its line, back to back, which could not + tell a word arriving from the whole line arriving; "miss, then" can. + - Dirty evictions and a last-word miss with 16 instructions after: + the longer gaps kept say the same. Misses 64 and 256 instructions + apart, and an ldm of 2 after a miss: as their neighbours. + - The pair rewrite, a replay of FLAC's decorrelation in DRAM. It never + behaved like the codec, because on the device the codec is not in + DRAM. + - The filter by where its state and code sit, and frames of adds: + they led to "adds in a loop", which measures the cause directly. + - Lines kept after a stream of misses, and after new lines in a set: + too coarse and too noisy to tell how the victim is chosen; "first + line kept" does. */ +#define TEST_CYC_ARCHIVE 0 + +#define ITERS 200000ul /* first try at loop iterations per run */ +#define UNROLL 16 + +#define X16(i) i i i i i i i i i i i i i i i i + +/* The data a benchmark touches: small, so the DRAM copy is a cache hit. */ +static unsigned long iram_buf[64] IBSS_ATTR __attribute__((aligned(32))); +static unsigned long dram_buf[64] __attribute__((aligned(32))); + +/* On PP5022/PP5024 plugins have 80 KB of IRAM, enough to map what a store + costs across most of it: the extra cycle a store pays when its code and + data share a memory turns out to depend on where in IRAM the data is. */ +#if (CONFIG_CPU == PP5022 || CONFIG_CPU == PP5024) && defined(USE_IRAM) +#define IRAM_MAP_BYTES (64 * 1024) +static unsigned long iram_map[IRAM_MAP_BYTES / 4] IBSS_ATTR; +#endif + + +typedef void (*benchfn)(unsigned long *p, unsigned long n); + +/* One benchmark, twice: once in IRAM, once in DRAM. r4-r9 are free for the + body; p and n come in whatever registers the compiler picks. */ +#define BENCH(name, setup, body) \ +static void ICODE_ATTR UNUSED_ATTR \ +name##_i(unsigned long *p, unsigned long n) \ +{ \ + asm volatile (setup "1:\n" X16(body) \ + " subs %[n], %[n], #1\n" \ + " bne 1b\n" \ + : [n] "+r" (n) : [p] "r" (p) \ + : "r4", "r5", "r6", "r7", "r8", "r9", "cc", "memory"); \ +} \ +BENCH_D(name, setup, body) + +/* The DRAM copy alone, for bodies that call the helpers below, which a + branch from IRAM would not reach. lr is free for the body too. */ +#define BENCH_D(name, setup, body) \ +static void name##_d(unsigned long *p, unsigned long n) \ +{ \ + asm volatile (setup "1:\n" X16(body) \ + " subs %[n], %[n], #1\n" \ + " bne 1b\n" \ + : [n] "+r" (n) : [p] "r" (p) \ + : "r4", "r5", "r6", "r7", "r8", "r9", "lr", "cc", \ + "memory"); \ +} + +#define NOSETUP "" +#define MULSETUP " mov r6, #0x3f00\n orr r6, r6, #0xff\n mov r7, r6\n" +/* A full-width multiplier, so the ARM7TDMI makes all four passes. */ +#define MUL32SETUP " mvn r6, #0x80000000\n mov r7, r6\n" +/* Offsets for the register-offset loads: 4 bytes, or 1 word scaled. */ +#define OFFSETUP " mov r6, #4\n mov r7, #1\n" + +BENCH(b_empty, NOSETUP, "") +BENCH(b_alu, NOSETUP, " add r4, r5, #1\n") +BENCH(b_regshift, NOSETUP, " mov r4, r5, lsl r6\n") +BENCH(b_branch, NOSETUP, " b .+4\n") +BENCH(b_ldr, NOSETUP, " ldr r4, [%[p]]\n" + " ldr r5, [%[p], #4]\n") +BENCH(b_ldruse, NOSETUP, " ldr r4, [%[p]]\n add r5, r4, #1\n") +BENCH(b_ldrb, NOSETUP, " ldrb r4, [%[p]]\n" + " ldrb r5, [%[p], #4]\n") +BENCH(b_ldm2, NOSETUP, " ldmia %[p], { r4, r5 }\n") +BENCH(b_ldm4, NOSETUP, " ldmia %[p], { r4-r7 }\n") +BENCH(b_ldm6, NOSETUP, " ldmia %[p], { r4-r9 }\n") +BENCH(b_str, NOSETUP, " str r4, [%[p]]\n") +BENCH(b_stm2, NOSETUP, " stmia %[p], { r4, r5 }\n") +BENCH(b_stm4, NOSETUP, " stmia %[p], { r4-r7 }\n") +BENCH(b_str1, NOSETUP, " str r4, [%[p]]\n add r5, r5, #1\n") +BENCH(b_str2, NOSETUP, " str r4, [%[p]]\n add r5, r5, #1\n" + " add r6, r6, #1\n") +BENCH(b_stm2gap, NOSETUP, " stmia %[p], { r4, r5 }\n" + " add r6, r6, #1\n" + " add r7, r7, #1\n") +BENCH(b_strldr, NOSETUP, " str r4, [%[p]]\n" + " ldr r5, [%[p], #4]\n") +BENCH(b_ldrbuse, NOSETUP, " ldrb r4, [%[p]]\n add r5, r4, #1\n") +BENCH(b_ldm2use, NOSETUP, " ldmia %[p], { r4, r5 }\n" + " add r6, r5, #1\n") +BENCH(b_ldrh, NOSETUP, " ldrh r4, [%[p]]\n" + " ldrh r5, [%[p], #4]\n") +BENCH(b_ldrsh, NOSETUP, " ldrsh r4, [%[p]]\n" + " ldrsh r5, [%[p], #4]\n") +BENCH(b_ldrhuse, NOSETUP, " ldrh r4, [%[p]]\n add r5, r4, #1\n") +BENCH(b_ldrshuse, NOSETUP, " ldrsh r4, [%[p]]\n add r5, r4, #1\n") +BENCH(b_ldrreg, OFFSETUP, " ldr r4, [%[p], r6]\n" + " ldr r5, [%[p], r6]\n") +BENCH(b_ldrlsl, OFFSETUP, " ldr r4, [%[p], r7, lsl #2]\n" + " ldr r5, [%[p], r7, lsl #2]\n") +BENCH(b_ldrbreg, OFFSETUP, " ldrb r4, [%[p], r6]\n" + " ldrb r5, [%[p], r6]\n") +BENCH(b_ldrhreg, OFFSETUP, " ldrh r4, [%[p], r6]\n" + " ldrh r5, [%[p], r6]\n") +BENCH(b_ldrreguse, OFFSETUP, " ldr r4, [%[p], r6]\n" + " add r5, r4, #1\n") +/* The loop's subs leaves the carry set while n has not run out, and this + sets it for the first pass, so a "cc" instruction never executes. */ +#define CCSETUP " cmp r4, r4\n" +BENCH(b_addcc, CCSETUP, " addcc r4, r5, #1\n") +BENCH(b_ldrcc, CCSETUP, " ldrcc r4, [%[p]]\n") +BENCH(b_strcc, CCSETUP, " strcc r4, [%[p]]\n") +BENCH(b_ldmcc, CCSETUP, " ldmccia %[p], { r4-r7 }\n") +BENCH(b_mlacc, MULSETUP CCSETUP, " mlacc r4, r5, r6, r4\n") +BENCH(b_bcc, CCSETUP, " bcc .+4\n") +/* Stack traffic, just below sp so nothing live is touched. */ +BENCH(b_spstr, NOSETUP, " str r4, [sp, #-4]\n") +BENCH(b_spstm, NOSETUP, " stmdb sp, { r4-r7 }\n") +BENCH(b_spldr, NOSETUP, " ldr r4, [sp, #-4]\n") +BENCH(b_sppush, NOSETUP, " stmfd sp!, { r4-r7 }\n" + " ldmfd sp!, { r4-r7 }\n") +BENCH(b_mla, MULSETUP, " mla r4, r5, r6, r4\n") +BENCH(b_mla32, MUL32SETUP, " mla r4, r5, r6, r4\n") +BENCH(b_smlal32, MUL32SETUP, " smlal r4, r5, r8, r7\n") +#if ARM_ARCH >= 5 +BENCH(b_smulbb, MULSETUP, " smulbb r4, r5, r6\n") +BENCH(b_smlabb, MULSETUP, " smlabb r4, r5, r6, r4\n") +BENCH(b_smlalbb, MULSETUP, " smlalbb r4, r5, r8, r6\n") +BENCH(b_smulwb, MULSETUP, " smulwb r4, r5, r6\n") +BENCH(b_smlawb, MULSETUP, " smlawb r4, r5, r6, r4\n") +BENCH(b_clz, NOSETUP, " clz r4, r5\n") +BENCH(b_qadd, NOSETUP, " qadd r4, r5, r6\n") +BENCH(b_ldrd, NOSETUP, " ldrd r4, r5, [%[p]]\n") +BENCH(b_strd, NOSETUP, " strd r4, r5, [%[p]]\n") +BENCH(b_pld, NOSETUP, " pld [%[p]]\n") +#endif +#if ARM_ARCH >= 6 +BENCH(b_smmul, MULSETUP, " smmul r4, r5, r6\n") +BENCH(b_smmla, MULSETUP, " smmla r4, r5, r6, r4\n") +BENCH(b_smmulr, MULSETUP, " smmulr r4, r5, r6\n") +BENCH(b_smuad, MULSETUP, " smuad r4, r5, r6\n") +BENCH(b_smlad, MULSETUP, " smlad r4, r5, r6, r4\n") +BENCH(b_smlald, MULSETUP, " smlald r4, r5, r8, r6\n") +BENCH(b_umaal, MULSETUP, " umaal r4, r5, r8, r6\n") +BENCH(b_muluse, MULSETUP, " mul r4, r5, r6\n add r7, r4, #1\n") +BENCH(b_smulluse, MULSETUP, " smull r4, r5, r8, r6\n" + " add r7, r5, #1\n") +BENCH(b_ssat, NOSETUP, " ssat r4, #16, r5\n") +BENCH(b_rev, NOSETUP, " rev r4, r5\n") +BENCH(b_sxth, NOSETUP, " sxth r4, r5\n") +BENCH(b_pkhbt, NOSETUP, " pkhbt r4, r5, r6, lsl #16\n") +BENCH(b_sadd16, NOSETUP, " sadd16 r4, r5, r6\n") +BENCH(b_qadd16, NOSETUP, " qadd16 r4, r5, r6\n") +#endif +BENCH(b_smlal, MULSETUP, " smlal r4, r5, r8, r7\n") + +/* Calls and returns, results used by the next instruction, and conditional + instructions that execute: what compiled code does all the time and the + rows above do not. The helpers are called from DRAM code only. */ +void tc_leaf(void); +void tc_pop2(void); +void tc_poplr2(void); +void tc_pop10(void); +void tc_popne10(void); +__asm__(".text\n.align 2\n" + ".type tc_leaf, %function\n" + "tc_leaf:\n" + " bx lr\n" + ".type tc_pop2, %function\n" + "tc_pop2:\n" + " stmfd sp!, { r4, lr }\n" + " ldmfd sp!, { r4, pc }\n" + ".type tc_poplr2, %function\n" + "tc_poplr2:\n" + " stmfd sp!, { r4, lr }\n" + " ldmfd sp!, { r4, lr }\n" + " bx lr\n" + ".type tc_pop10, %function\n" + "tc_pop10:\n" + " stmfd sp!, { r4-r11, ip, lr }\n" + " ldmfd sp!, { r4-r11, ip, pc }\n" + ".type tc_popne10, %function\n" + "tc_popne10:\n" + " stmfd sp!, { r4-r11, ip, lr }\n" + " cmp sp, #0\n" + " ldmnefd sp!, { r4-r11, ip, pc }\n"); +/* The loop's subs leaves Z clear while n has not run out, and this clears + it for the first pass, so an "ne" instruction always executes. */ +#define NESETUP " cmp sp, #0\n" +BENCH_D(b_call, NOSETUP, " bl tc_leaf\n") +BENCH_D(b_callpop, NOSETUP, " bl tc_pop2\n") +#if TEST_CYC_ARCHIVE +BENCH_D(b_callpoplr, NOSETUP, " bl tc_poplr2\n") +#endif +BENCH_D(b_callpop10, NOSETUP, " bl tc_pop10\n") +#if TEST_CYC_ARCHIVE +BENCH_D(b_callpopne, NOSETUP, " bl tc_popne10\n") +#endif +BENCH_D(b_bx, NOSETUP, " adr r4, 2f\n bx r4\n2:\n") +BENCH_D(b_ldrpc, NOSETUP, " adr r4, 2f\n str r4, [%[p]]\n" + " ldr pc, [%[p]]\n2:\n") +BENCH_D(b_bne, NESETUP, " bne .+4\n") +#if TEST_CYC_ARCHIVE +BENCH_D(b_addne, NESETUP, " addne r4, r5, #1\n") +BENCH_D(b_ldmne, NESETUP, " ldmneia %[p], { r4-r7 }\n") +BENCH_D(b_stmne, NESETUP, " stmneia %[p], { r4-r7 }\n") +BENCH_D(b_ands, NOSETUP, " ands r4, r5, #63\n") +#endif +BENCH_D(b_orrasr, NOSETUP, " orr r4, r5, r6, asr #30\n") +#if TEST_CYC_ARCHIVE +BENCH_D(b_addasr, NOSETUP, " add r4, r5, r6, asr r7\n") +BENCH_D(b_ldm3, NOSETUP, " ldmia %[p], { r4, r5, lr }\n") +#endif +BENCH_D(b_ldm4first, NOSETUP, " ldmia %[p], { r4-r7 }\n" + " add r8, r4, #1\n") +#if TEST_CYC_ARCHIVE +BENCH_D(b_ldm4third, NOSETUP, " ldmia %[p], { r4-r7 }\n" + " add r8, r6, #1\n") +#endif +BENCH_D(b_ldm4last, NOSETUP, " ldmia %[p], { r4-r7 }\n" + " add r8, r7, #1\n") +#if TEST_CYC_ARCHIVE +BENCH_D(b_ldm4mla, NOSETUP, " ldmia %[p], { r4-r7 }\n" + " mla r8, r4, r5, r8\n") +BENCH_D(b_ldmwb, " mov r8, %[p]\n", + " ldmia r8!, { r4-r7 }\n sub r8, r8, #16\n") +BENCH_D(b_stmwb, " mov r8, %[p]\n", + " stmia r8!, { r4-r7 }\n sub r8, r8, #16\n") +BENCH_D(b_stmldm, " add r8, %[p], #32\n", + " stmia %[p], { r4-r7 }\n ldmia r8, { r4-r7 }\n") +#endif +BENCH_D(b_strldrsame, NOSETUP, " str r4, [%[p]]\n" + " ldr r5, [%[p]]\n") +#if TEST_CYC_ARCHIVE +BENCH_D(b_push10, NOSETUP, " stmfd sp!, { r4-r11, ip, lr }\n" + " ldmfd sp!, { r4-r11, ip, lr }\n") +#endif +BENCH_D(b_mlause, MULSETUP, " mla r4, r5, r6, r7\n" + " add r8, r4, #1\n") +BENCH_D(b_mlamul, MULSETUP, " mla r4, r5, r6, r7\n" + " mla r8, r4, r6, r7\n") +#if TEST_CYC_ARCHIVE +BENCH_D(b_muladd, MULSETUP, " mul r4, r5, r6\n" + " add r8, r4, #1\n") +#endif +BENCH_D(b_smullacc, MULSETUP, " smull r4, r5, r8, r6\n" + " add r7, r5, #1\n") +BENCH_D(b_ldrmla, MULSETUP, " ldr r4, [%[p]]\n" + " mla r8, r4, r6, r8\n") +#if TEST_CYC_ARCHIVE +BENCH_D(b_ldrshift, NOSETUP, " ldr r6, [%[p]]\n" + " add r4, r5, r4, asr r6\n") +#endif + +/* A pointer chase: each word holds the address of the next one, stride + bytes on, wrapping at the end. Sixteen dependent loads per iteration, so + the loop's two instructions are a small share and every load waits for the + last. */ +static void chase(unsigned long *start, unsigned long n) +{ + asm volatile ( + " mov r4, %[s]\n" + "1:\n" + X16(" ldr r4, [r4]\n") + " subs %[n], %[n], #1\n" + " bne 1b\n" + : [n] "+r" (n) : [s] "r" (start) : "r4", "cc", "memory"); +} + +/* Sequential loads through sbuf, which is sbytes long (a multiple of 512), + rewinding at the end. r4-r7 take the data and nothing reads it. p and + n are early-clobber: p starts equal to sbuf, and gcc would otherwise give + p and base one register, making the rewind a no-op. */ +static unsigned long *sbuf; +static unsigned long sbytes; + +#define STREAM(name, body) \ +static void name(unsigned long *unused, unsigned long n) \ +{ \ + unsigned long *p = sbuf, *end = sbuf + sbytes / 4; \ + (void)unused; \ + asm volatile ("1:\n" X16(body) \ + " cmp %[p], %[end]\n" \ + " movhs %[p], %[base]\n" \ + " subs %[n], %[n], #1\n" \ + " bne 1b\n" \ + : [n] "+&r" (n), [p] "+&r" (p) \ + : [end] "r" (end), [base] "r" (sbuf) \ + : "r4", "r5", "r6", "r7", "cc", "memory"); \ +} +STREAM(stream_word, " ldr r4, [%[p]], #4\n") +STREAM(stream_line, " ldr r4, [%[p]], #32\n") +STREAM(stream_ldm4, " ldmia %[p]!, { r4-r7 }\n") +/* The same with stores, to price a store whose line is not cached. */ +STREAM(wstream_word, " str r4, [%[p]], #4\n") +STREAM(wstream_line, " str r4, [%[p]], #32\n") +STREAM(wstream_stm4, " stmia %[p]!, { r4-r7 }\n") + +/* A load per line with work between that touches no memory, to see whether + a miss costs less when the next access is not waiting on it. */ +#define X8(i) i i i i i i i i +#define WORK8 X8(" add r5, r5, #1\n") +STREAM(stream_work8, " ldr r4, [%[p]], #32\n" WORK8) +STREAM(stream_work16, " ldr r4, [%[p]], #32\n" WORK8 WORK8) +STREAM(stream_work32, " ldr r4, [%[p]], #32\n" WORK8 WORK8 WORK8 WORK8) + +/* A miss on the last word of each line, and on its fourth: if the fill + brings the word asked for first these cost what a miss on the first word + does, and if it runs in order from the start of the line they cost + more. */ +STREAM(stream_last, " ldr r4, [%[p], #28]\n add %[p], %[p], #32\n") +STREAM(stream_mid, " ldr r4, [%[p], #12]\n add %[p], %[p], #32\n") +STREAM(stream_first, " ldr r4, [%[p]]\n add %[p], %[p], #32\n") +#if TEST_CYC_ARCHIVE +/* The same misses with the rest of the line read straight after, a word + at a time, in order from the start. */ +#define REST_OF_LINE \ + " ldr r5, [%[p]]\n ldr r5, [%[p], #4]\n" \ + " ldr r5, [%[p], #8]\n ldr r5, [%[p], #16]\n" \ + " ldr r5, [%[p], #20]\n ldr r5, [%[p], #24]\n" \ + " add %[p], %[p], #32\n" +STREAM(stream_last_rest, " ldr r4, [%[p], #28]\n" REST_OF_LINE) +STREAM(stream_mid_rest, " ldr r4, [%[p], #12]\n" REST_OF_LINE) +#endif + +/* A miss whose line is then written, so that every line a later miss + evicts is dirty and has to go back to memory first: one store dirties + half the line on the ARM926, two the whole of it. */ +STREAM(stream_dirty4, " ldr r4, [%[p]]\n str r4, [%[p]]\n" + " add %[p], %[p], #32\n") +STREAM(stream_dirty8, " ldr r4, [%[p]]\n str r4, [%[p]]\n" + " str r4, [%[p], #16]\n" + " add %[p], %[p], #32\n") + +/* The dirty eviction and the miss on a last word again, with work after + that touches no memory: whether the write-back, and the fill from the + last word, hold the core or only the next miss. */ +#define DIRTY8 " ldr r4, [%[p]]\n str r4, [%[p]]\n" \ + " str r4, [%[p], #16]\n add %[p], %[p], #32\n" +#define WORK16 WORK8 WORK8 +#define WORK32 WORK16 WORK16 +#if TEST_CYC_ARCHIVE +STREAM(stream_dirty8_w16, DIRTY8 WORK16) +#endif +STREAM(stream_dirty8_w32, DIRTY8 WORK32) +STREAM(stream_dirty8_w64, DIRTY8 WORK32 WORK32) +STREAM(stream_dirty8_w96, DIRTY8 WORK32 WORK32 WORK32) +#define LAST " ldr r4, [%[p], #28]\n add %[p], %[p], #32\n" +#if TEST_CYC_ARCHIVE +STREAM(stream_last_w16, LAST WORK16) +#endif +STREAM(stream_last_w32, LAST WORK32) + +/* A miss with a long way to the next one, and what else is read from its + line straight after: whether the stall is all there is when nothing + follows, and what a second word of the line, or the rest of an ldm, + waits for. */ +#define WORK64 WORK32 WORK32 +#define LINE0 " ldr r4, [%[p]]\n" +#define NEXTLINE " add %[p], %[p], #32\n" +STREAM(gap_32, LINE0 NEXTLINE WORK32) +#if TEST_CYC_ARCHIVE +STREAM(gap_64, LINE0 NEXTLINE WORK64) +#endif +STREAM(gap_128, LINE0 NEXTLINE WORK64 WORK64) +#if TEST_CYC_ARCHIVE +STREAM(gap_256, LINE0 NEXTLINE WORK64 WORK64 WORK64 WORK64) +#endif +STREAM(gap_w1, LINE0 " ldr r5, [%[p], #4]\n" NEXTLINE WORK32) +STREAM(gap_w7, LINE0 " ldr r5, [%[p], #28]\n" NEXTLINE WORK32) +#if TEST_CYC_ARCHIVE +STREAM(gap_ldm2, " ldmia %[p], { r4, r5 }\n" NEXTLINE WORK32) +#endif +STREAM(gap_ldm4, " ldmia %[p], { r4-r7 }\n" NEXTLINE WORK32) +STREAM(gap_ldm4mid, " add r5, %[p], #8\n" + " ldmia r5, { r4-r7 }\n" NEXTLINE WORK32) +STREAM(gap_str, LINE0 " str r4, [%[p]]\n" NEXTLINE WORK32) + +/* A copy, four words at a time, to 128 KB further on: line fills and + buffered writes both wanting the bus, as in a memcpy between buffers + that are not in the cache. */ +STREAM(copy_4, " ldmia %[p], { r4-r7 }\n" + " add r4, %[p], #0x20000\n" + " stmia r4, { r4-r7 }\n" + " add %[p], %[p], #16\n") + +/* The chase with work after each load, for a miss far from the last. */ +static void chase_work32(unsigned long *start, unsigned long n) +{ + asm volatile ( + " mov r4, %[s]\n" + "1:\n" + X16(" ldr r4, [r4]\n" WORK32) + " subs %[n], %[n], #1\n" + " bne 1b\n" + : [n] "+r" (n) : [s] "r" (start) : "r4", "r5", "cc", "memory"); +} + +#if TEST_CYC_ARCHIVE +/* What a codec does with a block of two channels: each buffer is filtered + in place, one after the other (pair_filter), then both are rewritten + together, four words of each per pass (pair_pass, the loop of the FLAC + decorrelation). pair_frame is all three and pair_filters the first two, + so their difference is the passes alone, as they find the cache. */ +void pair_filter(unsigned long *p, unsigned long passes); +void pair_pass(unsigned long *a, unsigned long *b, unsigned long passes); +__asm__(".text\n.align 2\n.type pair_filter, %function\n" + "pair_filter:\n" + " stmfd sp!, { r4-r7 }\n" + "1:\n" + " ldmia r0, { r4-r7 }\n" + " stmia r0!, { r4-r7 }\n" + " subs r1, r1, #1\n" + " bne 1b\n" + " ldmfd sp!, { r4-r7 }\n" + " bx lr\n" + ".type pair_pass, %function\n" + "pair_pass:\n" + " stmfd sp!, { r4-r11 }\n" + " mov r3, #0\n" + "1:\n" + " ldmia r0, { r4-r7 }\n" + " ldmia r1, { r8-r11 }\n" + " mov r4, r4, lsl r3\n" + " mov r8, r8, lsl r3\n" + " mov r5, r5, lsl r3\n" + " mov r9, r9, lsl r3\n" + " mov r6, r6, lsl r3\n" + " mov r10, r10, lsl r3\n" + " mov r7, r7, lsl r3\n" + " mov r11, r11, lsl r3\n" + " stmia r0!, { r4-r7 }\n" + " stmia r1!, { r8-r11 }\n" + " subs r2, r2, #1\n" + " bne 1b\n" + " ldmfd sp!, { r4-r11 }\n" + " bx lr\n"); + +static unsigned long *pair_a, *pair_b; +static unsigned long pair_passes; /* 16 bytes of each buffer a pass */ + +static void pair_filters(unsigned long *unused, unsigned long n) +{ + (void)unused; + while (n--) + { + pair_filter(pair_a, pair_passes); + pair_filter(pair_b, pair_passes); + } +} + +static void pair_frame(unsigned long *unused, unsigned long n) +{ + (void)unused; + while (n--) + { + pair_filter(pair_a, pair_passes); + pair_filter(pair_b, pair_passes); + pair_pass(pair_a, pair_b, pair_passes); + } +} +#endif + +/* Taken branches, one per cache line of DRAM code, each to the next, so + over more code than the cache holds every one is a fetch miss. Lines + are 16 bytes on the PP502x and 32 on the ARM9 targets. */ +#if ARM_ARCH >= 5 +#define ICHAIN_PAD "28" +#else +#define ICHAIN_PAD "12" +#endif +#define ICHAIN(name, lines) \ +static void name(unsigned long *unused, unsigned long n) \ +{ \ + (void)unused; \ + asm volatile ("1:\n" \ + " .rept " #lines "\n" \ + " b 2f\n" \ + " .space " ICHAIN_PAD "\n" \ + "2:\n" \ + " .endr\n" \ + " subs %[n], %[n], #1\n" \ + " bne 1b\n" \ + : [n] "+r" (n) : : "cc"); \ +} +#define ICHAIN_SMALL 32 /* 512 bytes, or 1 KB */ +#define ICHAIN_BIG 2048 /* 32 KB, or 64 KB */ +ICHAIN(ichain_small, 32) +ICHAIN(ichain_big, 2048) +ICHAIN(ichain_1, 1) +ICHAIN(ichain_2, 2) +ICHAIN(ichain_3, 3) +ICHAIN(ichain_4, 4) +ICHAIN(ichain_5, 5) +ICHAIN(ichain_6, 6) +ICHAIN(ichain_7, 7) +ICHAIN(ichain_8, 8) +ICHAIN(ichain_9, 9) +ICHAIN(ichain_10, 10) +ICHAIN(ichain_12, 12) +ICHAIN(ichain_16, 16) +ICHAIN(ichain_64, 64) +ICHAIN(ichain_128, 128) +ICHAIN(ichain_256, 256) + +/* Adds in a row and nothing else, the loop starting on a cache line: how + much code a loop can run through before an instruction costs more than + its cycle. */ +#define SEQ(name, count) \ +static void name(unsigned long *unused, unsigned long n) \ +{ \ + (void)unused; \ + asm volatile (" b 1f\n" \ + " .balign 32\n" \ + "1:\n" \ + " .rept " #count "\n" \ + " add r4, r4, #1\n" \ + " .endr\n" \ + " subs %[n], %[n], #1\n" \ + " bne 1b\n" \ + : [n] "+r" (n) : : "r4", "cc"); \ +} +SEQ(seq_6, 6) +SEQ(seq_14, 14) +SEQ(seq_22, 22) +SEQ(seq_30, 30) +SEQ(seq_38, 38) +SEQ(seq_46, 46) +SEQ(seq_54, 54) +SEQ(seq_62, 62) +SEQ(seq_70, 70) +SEQ(seq_78, 78) +SEQ(seq_94, 94) +SEQ(seq_126, 126) +SEQ(seq_254, 254) +SEQ(seq_510, 510) +SEQ(seq_1022, 1022) +SEQ(seq_2046, 2046) + +/* The same chain in scattered order: a full-period linear congruence over + the nodes (bytes / stride, a power of two), so consecutive loads are far + apart instead of next to each other. */ +static void make_chain_scattered(unsigned char *buf, unsigned long bytes, + unsigned long stride) +{ + unsigned long i, nodes = bytes / stride; + for (i = 0; i < nodes; i++) + *(unsigned long *)(buf + i * stride) = (unsigned long) + (buf + ((i * 1664525ul + 1013904223ul) & (nodes - 1)) * stride); +} + +static void make_chain(unsigned char *buf, unsigned long bytes, + unsigned long stride) +{ + unsigned long off; + for (off = 0; off < bytes; off += stride) + *(unsigned long *)(buf + off) = + (unsigned long)(buf + (off + stride) % bytes); +} + +/* The finest free-running timer each SoC has. Plugins do not see the SoC + headers, so the registers are named here, as USEC_TIMER (or the + S5L8700's 5 us counter) is defined for each in firmware/. Anywhere + else the tick has to do. */ +#if CONFIG_CPU == PP5020 || CONFIG_CPU == PP5022 || CONFIG_CPU == PP5024 +#define NOW() (*(volatile unsigned long *)0x60005010) +#define PER_SEC 1000000ll +#define TIMER_NAME "PP502x usec timer" +#elif CONFIG_CPU == S5L8700 || CONFIG_CPU == S5L8701 +#define NOW() (*(volatile unsigned long *)0x3C700084) /* TICNTL */ +#define PER_SEC 200000ll +#define TIMER_NAME "S5L8700 5 usec timer" +#elif CONFIG_CPU == PP5002 +#define NOW() (*(volatile unsigned long *)0xcf001110) +#define PER_SEC 1000000ll +#define TIMER_NAME "PP5002 usec timer" +#elif CONFIG_CPU == S5L8702 || CONFIG_CPU == S5L8720 +#define NOW() (*(volatile unsigned long *)0x3C7000B4) /* TECNT */ +#define PER_SEC 1000000ll +#define TIMER_NAME "S5L8702/8720 usec timer" +#elif CONFIG_CPU == TCC7801 +#define NOW() (*(volatile unsigned long *)0xF3003094) /* TC32MCNT */ +#define PER_SEC 1000000ll +#define TIMER_NAME "TCC7801 usec timer" +#elif CONFIG_CPU == IMX233 +#define NOW() (*(volatile unsigned long *)0x8001c0c0) /* DIGCTL usec */ +#define PER_SEC 1000000ll +#define TIMER_NAME "i.MX233 usec timer" +#elif CONFIG_CPU == AS3525v2 +/* No free-running counter, but the kernel tick's timer counts down from + 15000 at 1.5 MHz, so the tick and that together are one. */ +static unsigned long as3525_now(void) +{ + long t; + unsigned long v; + do { + t = *rb->current_tick; + v = *(volatile unsigned long *)0xC8040024; /* TIMER2_VALUE */ + } while (t != *rb->current_tick); + return (unsigned long)t * 15000 + (15000 - v); +} +#define NOW() as3525_now() +#define PER_SEC 1500000ll +#define TIMER_NAME "AS3525v2 tick timer, 2/3 usec" +#else +#define NOW() (*rb->current_tick) +#define PER_SEC ((long long)HZ) +#define TICK_TIMED +#define TIMER_NAME "the tick: runs of a second or more" +#endif + +#ifdef TICK_TIMED +#define MIN_ELAPSED 100ul /* a second of ticks */ +#else +#define MIN_ELAPSED ((unsigned long)(PER_SEC / 50)) /* 20 ms */ +#endif + +/* Hundredths of a cycle per loop iteration. The run doubles in length until + the clock has moved far enough for the figure to be good to a percent. */ +static long long cpi_from(benchfn fn, unsigned long *p, unsigned long n) +{ + unsigned long t0, t1; + fn(p, n < 64 ? n : 64); /* warm the caches */ + for (;;) + { + t0 = NOW(); + fn(p, n); + t1 = NOW(); + if (t1 - t0 >= MIN_ELAPSED || n >= (1ul << 28)) + break; + n *= 2; + } + return (long long)(t1 - t0) * *rb->cpu_frequency * 100 / PER_SEC / n; +} + +static long long cpi(benchfn fn, unsigned long *p) +{ + return cpi_from(fn, p, ITERS); +} + +static int fd = -1; + +/* The screen keeps as many of the latest lines as fit, older ones scrolling + off the top; the file gets every line. */ +#define MAXROWS 64 +static char shown[MAXROWS][48]; +static int nshown; + +static void say(const char *fmt, ...) +{ + char buf[96]; + int i, rows; + va_list ap; + va_start(ap, fmt); + rb->vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + if (fd >= 0) + { + rb->write(fd, buf, rb->strlen(buf)); + rb->write(fd, "\n", 1); + } + rows = LCD_HEIGHT / rb->font_get(FONT_SYSFIXED)->height; + if (rows > MAXROWS) + rows = MAXROWS; + if (rows < 1) + rows = 1; + if (nshown >= rows) + { + rb->memmove(shown[0], shown[nshown - rows + 1], + (rows - 1) * sizeof(shown[0])); + nshown = rows - 1; + } + rb->strlcpy(shown[nshown++], buf, sizeof(shown[0])); + rb->lcd_clear_display(); + for (i = 0; i < nshown; i++) + rb->lcd_puts(0, i, shown[i]); + rb->lcd_update(); +} + +/* Hundredths of a cycle per instruction, over the loop's own cost. */ +static int per_insn(long long cyc, long long empty) +{ + return (int)((cyc - empty) / UNROLL); +} + +/* Multiplies by the size of each operand: Rm in r5 and Rs in r6, each + 14 bits, 24 bits, 31 bits or a small negative number. */ +#define RM14 " mov r5, #0x3f00\n orr r5, r5, #0xff\n" +#define RM24 " mov r5, #0x7f0000\n orr r5, r5, #0xff00\n" +#define RM31 " mvn r5, #0x80000000\n" +#define RMNEG " mvn r5, #0x3f00\n" +#define RS14 " mov r6, #0x3f00\n orr r6, r6, #0xff\n" +#define RS24 " mov r6, #0x7f0000\n orr r6, r6, #0xff00\n" +#define RS31 " mvn r6, #0x80000000\n" +#define RSNEG " mvn r6, #0x3f00\n" +#define MLA " mla r4, r5, r6, r4\n" +#define MUL " mul r4, r5, r6\n" +#define SMULL " smull r8, r9, r5, r6\n" +#define SMLAL " smlal r8, r9, r5, r6\n" +BENCH_D(b_mla_14_14, RM14 RS14, MLA) +#if TEST_CYC_ARCHIVE +BENCH_D(b_mla_24_14, RM24 RS14, MLA) +#endif +BENCH_D(b_mla_31_14, RM31 RS14, MLA) +#if TEST_CYC_ARCHIVE +BENCH_D(b_mla_n_14, RMNEG RS14, MLA) +BENCH_D(b_mla_14_24, RM14 RS24, MLA) +#endif +BENCH_D(b_mla_14_31, RM14 RS31, MLA) +BENCH_D(b_mla_14_n, RM14 RSNEG, MLA) +#if TEST_CYC_ARCHIVE +BENCH_D(b_mla_24_24, RM24 RS24, MLA) +#endif +BENCH_D(b_mla_31_31, RM31 RS31, MLA) +#if TEST_CYC_ARCHIVE +BENCH_D(b_mul_14_14, RM14 RS14, MUL) +BENCH_D(b_mul_31_14, RM31 RS14, MUL) +BENCH_D(b_mul_14_31, RM14 RS31, MUL) +BENCH_D(b_mul_31_31, RM31 RS31, MUL) +#endif +BENCH_D(b_smull_14_14, RM14 RS14, SMULL) +#if TEST_CYC_ARCHIVE +BENCH_D(b_smull_31_14, RM31 RS14, SMULL) +BENCH_D(b_smull_14_31, RM14 RS31, SMULL) +#endif +BENCH_D(b_smull_31_31, RM31 RS31, SMULL) +#if TEST_CYC_ARCHIVE +BENCH_D(b_smlal_14_14, RM14 RS14, SMLAL) +BENCH_D(b_smlal_31_31, RM31 RS31, SMLAL) +#endif + +struct row { + const char *name; + benchfn i, d; + int per; /* instructions per X16 copy */ + int alu; /* ALU cycles per copy, subtracted */ +}; + +/* DRAM code only; a call row is the whole call, there and back, and the + pairs are both instructions together. */ +#define D(fn) fn##_d, fn##_d +static const struct row B2[] = { + { "bl, bx lr ", D(b_call), 1, 0 }, + { "bl,pop2 pc", D(b_callpop), 1, 0 }, +#if TEST_CYC_ARCHIVE + { "bl,pop2 lr", D(b_callpoplr), 1, 0 }, +#endif + { "bl,pop10pc", D(b_callpop10), 1, 0 }, +#if TEST_CYC_ARCHIVE + { "bl,popne10", D(b_callpopne), 1, 1 }, +#endif + { "bx reg ", D(b_bx), 1, 1 }, + { "ldr pc ", D(b_ldrpc), 1, 2 }, + { "bne taken ", D(b_bne), 1, 0 }, +#if TEST_CYC_ARCHIVE + { "addne ", D(b_addne), 1, 0 }, + { "ldmne 4 ", D(b_ldmne), 1, 0 }, + { "stmne 4 ", D(b_stmne), 1, 0 }, + { "ands ", D(b_ands), 1, 0 }, +#endif + { "orr asr # ", D(b_orrasr), 1, 0 }, +#if TEST_CYC_ARCHIVE + { "add asr Rs", D(b_addasr), 1, 0 }, + { "ldm 3 ", D(b_ldm3), 1, 0 }, +#endif + { "ldm4+use 1", D(b_ldm4first), 1, 0 }, +#if TEST_CYC_ARCHIVE + { "ldm4+use 3", D(b_ldm4third), 1, 0 }, +#endif + { "ldm4+use 4", D(b_ldm4last), 1, 0 }, +#if TEST_CYC_ARCHIVE + { "ldm4+mla 1", D(b_ldm4mla), 1, 0 }, + { "ldm 4 wb ", D(b_ldmwb), 1, 1 }, + { "stm 4 wb ", D(b_stmwb), 1, 1 }, + { "stm4+ldm4 ", D(b_stmldm), 1, 0 }, +#endif + { "str+ldr = ", D(b_strldrsame), 1, 0 }, +#if TEST_CYC_ARCHIVE + { "push+pop10", D(b_push10), 1, 0 }, +#endif + { "mla+use ", D(b_mlause), 1, 0 }, + { "mla+mla Rm", D(b_mlamul), 1, 0 }, +#if TEST_CYC_ARCHIVE + { "mul+use ", D(b_muladd), 1, 0 }, +#endif + { "smull+use ", D(b_smullacc), 1, 0 }, + { "ldr+mla ", D(b_ldrmla), 1, 0 }, +#if TEST_CYC_ARCHIVE + { "ldr+asr Rs", D(b_ldrshift), 1, 0 }, +#endif +}; +#define NB2 ((int)(sizeof(B2) / sizeof(B2[0]))) + +/* Multiplies, named by the bits of Rm and of Rs; n is a small negative. */ +static const struct row B3[] = { + { "mla 14x14 ", D(b_mla_14_14), 1, 0 }, +#if TEST_CYC_ARCHIVE + { "mla 24x14 ", D(b_mla_24_14), 1, 0 }, +#endif + { "mla 31x14 ", D(b_mla_31_14), 1, 0 }, +#if TEST_CYC_ARCHIVE + { "mla nx14 ", D(b_mla_n_14), 1, 0 }, + { "mla 14x24 ", D(b_mla_14_24), 1, 0 }, +#endif + { "mla 14x31 ", D(b_mla_14_31), 1, 0 }, + { "mla 14xn ", D(b_mla_14_n), 1, 0 }, +#if TEST_CYC_ARCHIVE + { "mla 24x24 ", D(b_mla_24_24), 1, 0 }, +#endif + { "mla 31x31 ", D(b_mla_31_31), 1, 0 }, +#if TEST_CYC_ARCHIVE + { "mul 14x14 ", D(b_mul_14_14), 1, 0 }, + { "mul 31x14 ", D(b_mul_31_14), 1, 0 }, + { "mul 14x31 ", D(b_mul_14_31), 1, 0 }, + { "mul 31x31 ", D(b_mul_31_31), 1, 0 }, +#endif + { "smull14x14", D(b_smull_14_14), 1, 0 }, +#if TEST_CYC_ARCHIVE + { "smull31x14", D(b_smull_31_14), 1, 0 }, + { "smull14x31", D(b_smull_14_31), 1, 0 }, +#endif + { "smull31x31", D(b_smull_31_31), 1, 0 }, +#if TEST_CYC_ARCHIVE + { "smlal14x14", D(b_smlal_14_14), 1, 0 }, + { "smlal31x31", D(b_smlal_31_31), 1, 0 }, +#endif +}; +#define NB3 ((int)(sizeof(B3) / sizeof(B3[0]))) + +static const struct row B[] = { + { "add ", b_alu_i, b_alu_d, 1, 0 }, + { "mov lsl Rs", b_regshift_i, b_regshift_d, 1, 0 }, + { "b taken ", b_branch_i, b_branch_d, 1, 0 }, + { "ldr ", b_ldr_i, b_ldr_d, 2, 0 }, + { "ldr+use ", b_ldruse_i, b_ldruse_d, 1, 0 }, + { "ldrb ", b_ldrb_i, b_ldrb_d, 2, 0 }, + { "ldm 2 ", b_ldm2_i, b_ldm2_d, 1, 0 }, + { "ldm 4 ", b_ldm4_i, b_ldm4_d, 1, 0 }, + { "ldm 6 ", b_ldm6_i, b_ldm6_d, 1, 0 }, + { "str ", b_str_i, b_str_d, 1, 0 }, + { "stm 2 ", b_stm2_i, b_stm2_d, 1, 0 }, + { "stm 4 ", b_stm4_i, b_stm4_d, 1, 0 }, + { "str,1 alu ", b_str1_i, b_str1_d, 1, 1 }, + { "str,2 alu ", b_str2_i, b_str2_d, 1, 2 }, + { "stm2,2 alu", b_stm2gap_i, b_stm2gap_d, 1, 2 }, + { "str+ldr ", b_strldr_i, b_strldr_d, 1, 0 }, + { "ldrb+use ", b_ldrbuse_i, b_ldrbuse_d, 1, 1 }, + { "ldm2+use ", b_ldm2use_i, b_ldm2use_d, 1, 1 }, + { "ldrh ", b_ldrh_i, b_ldrh_d, 2, 0 }, + { "ldrsh ", b_ldrsh_i, b_ldrsh_d, 2, 0 }, + { "ldrh+use ", b_ldrhuse_i, b_ldrhuse_d, 1, 1 }, + { "ldrsh+use ", b_ldrshuse_i, b_ldrshuse_d, 1, 1 }, + { "ldr [Rn,Rm", b_ldrreg_i, b_ldrreg_d, 2, 0 }, + { "ldr Rm lsl", b_ldrlsl_i, b_ldrlsl_d, 2, 0 }, + { "ldrb Rm ", b_ldrbreg_i, b_ldrbreg_d, 2, 0 }, + { "ldrh Rm ", b_ldrhreg_i, b_ldrhreg_d, 2, 0 }, + { "ldr Rm+use", b_ldrreguse_i, b_ldrreguse_d, 1, 1 }, + { "addcc fail", b_addcc_i, b_addcc_d, 1, 0 }, + { "ldrcc fail", b_ldrcc_i, b_ldrcc_d, 1, 0 }, + { "strcc fail", b_strcc_i, b_strcc_d, 1, 0 }, + { "ldmcc fail", b_ldmcc_i, b_ldmcc_d, 1, 0 }, + { "mlacc fail", b_mlacc_i, b_mlacc_d, 1, 0 }, + { "bcc fail ", b_bcc_i, b_bcc_d, 1, 0 }, + { "mla 14b Rs", b_mla_i, b_mla_d, 1, 0 }, + { "smlal 14b ", b_smlal_i, b_smlal_d, 1, 0 }, + { "mla 32b Rs", b_mla32_i, b_mla32_d, 1, 0 }, + { "smlal 32b ", b_smlal32_i, b_smlal32_d, 1, 0 }, +#if ARM_ARCH >= 5 + { "smulbb ", b_smulbb_i, b_smulbb_d, 1, 0 }, + { "smlabb acc", b_smlabb_i, b_smlabb_d, 1, 0 }, + { "smlalbb ", b_smlalbb_i, b_smlalbb_d, 1, 0 }, + { "smulwb ", b_smulwb_i, b_smulwb_d, 1, 0 }, + { "smlawb acc", b_smlawb_i, b_smlawb_d, 1, 0 }, + { "clz ", b_clz_i, b_clz_d, 1, 0 }, + { "qadd ", b_qadd_i, b_qadd_d, 1, 0 }, + { "ldrd ", b_ldrd_i, b_ldrd_d, 1, 0 }, + { "strd ", b_strd_i, b_strd_d, 1, 0 }, + { "pld ", b_pld_i, b_pld_d, 1, 0 }, +#endif +#if ARM_ARCH >= 6 + { "smmul ", b_smmul_i, b_smmul_d, 1, 0 }, + { "smmla acc ", b_smmla_i, b_smmla_d, 1, 0 }, + { "smmulr ", b_smmulr_i, b_smmulr_d, 1, 0 }, + { "smuad ", b_smuad_i, b_smuad_d, 1, 0 }, + { "smlad acc ", b_smlad_i, b_smlad_d, 1, 0 }, + { "smlald ", b_smlald_i, b_smlald_d, 1, 0 }, + { "umaal ", b_umaal_i, b_umaal_d, 1, 0 }, + { "mul+use ", b_muluse_i, b_muluse_d, 1, 1 }, + { "smull+use ", b_smulluse_i, b_smulluse_d, 1, 1 }, + { "ssat ", b_ssat_i, b_ssat_d, 1, 0 }, + { "rev ", b_rev_i, b_rev_d, 1, 0 }, + { "sxth ", b_sxth_i, b_sxth_d, 1, 0 }, + { "pkhbt ", b_pkhbt_i, b_pkhbt_d, 1, 0 }, + { "sadd16 ", b_sadd16_i, b_sadd16_d, 1, 0 }, + { "qadd16 ", b_qadd16_i, b_qadd16_d, 1, 0 }, +#endif +}; +#define NB ((int)(sizeof(B) / sizeof(B[0]))) + +static void fmt(char *out, int v) +{ + rb->snprintf(out, 8, "%3d.%02d", v / 100, v < 0 ? -v % 100 : v % 100); +} + +/* Stack rows: where the stack is depends on the thread, so these are run + on the caller's thread, with code in IRAM and in DRAM. */ +static const struct { + const char *name; + benchfn i, d; +} SP[] = { + { "str [sp] ", b_spstr_i, b_spstr_d }, + { "stm 4 [sp]", b_spstm_i, b_spstm_d }, + { "ldr [sp] ", b_spldr_i, b_spldr_d }, + { "push+pop 4", b_sppush_i, b_sppush_d }, +}; +#define NSP ((int)(sizeof(SP) / sizeof(SP[0]))) + +#ifdef USE_IRAM +static long long sp_empty_i; +#endif +static long long sp_empty_d; +static int sp_res[NSP][2]; +static unsigned long sp_addr; +static volatile bool sp_done; + +static void stack_rows(void) +{ + int k; + asm volatile ("mov %0, sp" : "=r" (sp_addr)); + for (k = 0; k < NSP; k++) + { +#ifdef USE_IRAM + sp_res[k][0] = per_insn(cpi(SP[k].i, dram_buf), sp_empty_i); +#endif + sp_res[k][1] = per_insn(cpi(SP[k].d, dram_buf), sp_empty_d); + } + sp_done = true; +} + +static void say_stack_rows(const char *thread) +{ + int k; + say("stack rows, %s thread, sp %08lx", thread, sp_addr); +#ifdef USE_IRAM + say("cyc/insn code:IRAM DRAM"); +#endif + for (k = 0; k < NSP; k++) + { + char a[8], b[8]; + fmt(b, sp_res[k][1]); +#ifdef USE_IRAM + fmt(a, sp_res[k][0]); + say("%s %s %s", SP[k].name, a, b); +#else + (void)a; + say("%s %s", SP[k].name, b); +#endif + } +} + +/* A real kernel, a stage at a time: libtta's hybrid_filter (filter_arm.S), + its error > 0 path, cut off after each stage in turn, to find where a + whole function costs more than its instructions do one by one. Each + stage is the ones before it and: + 0 the frame: push and pop of ten registers, through the pc + 1 the three-word load of the state, five adds, the branches + 2 ldm 4, ldm 4, four adds, stm 4: the first weights + 3 ldm 4 and four mla + 4 stage 2 again for the next four weights + 5 stage 3 again + 6 ldr, ldr, add with a register shift, str, ldr, add, ands, stm 2 + 7 mov, four orr with a shift, three shifts, three subs + 8 the two conditional stm 4 and the conditional return: the whole + function, shifting its delay lines every sixteenth call */ +#define TCF_ADD4 " add r5, r5, r9\n add r6, r6, r10\n" \ + " add r7, r7, r11\n add r8, r8, r12\n" +#define TCF_MLA4 " mla lr, r5, r9, lr\n mla lr, r6, r10, lr\n" \ + " mla lr, r7, r11, lr\n mla lr, r8, r12, lr\n" +__asm__(".text\n.align 2\n" + ".macro TCF name, stage, sub=0, step=4\n" + ".type \\name, %function\n" + "\\name:\n" + " stmdb sp!, { r4-r12, lr }\n" + ".if \\stage >= 1\n" + " ldmia r0, { r5, r6, lr }\n" + " add r2, r0, #148\n" + " add r3, r0, #52\n" + " add r4, r0, #20\n" + " add r2, r2, r5\n" + " add r3, r3, r5\n" + " cmp r6, #0\n" + " bmi 9f\n" + " bne 1f\n" + " b 9f\n" + "1:\n" + ".endif\n" + ".if \\stage >= 2\n" + " ldmia r4, { r5, r6, r7, r8 }\n" + " ldmia r3!, { r9, r10, r11, r12 }\n" + TCF_ADD4 + " stmia r4!, { r5, r6, r7, r8 }\n" + ".endif\n" + ".if \\stage >= 3\n" + " ldmia r2!, { r9, r10, r11, r12 }\n" + TCF_MLA4 + ".endif\n" + ".if \\stage >= 4\n" + " ldmia r4, { r5, r6, r7, r8 }\n" + " ldmia r3!, { r9, r10, r11, r12 }\n" + TCF_ADD4 + " stmia r4!, { r5, r6, r7, r8 }\n" + ".endif\n" + ".if \\stage >= 5\n" + " ldmia r2!, { r9, r10, r11, r12 }\n" + TCF_MLA4 + ".endif\n" + ".irp k, 1, 2, 3, 4, 5, 6, 7, 8\n" + ".if (\\stage >= 6) | (\\sub >= \\k)\n" + ".if \\k == 1\n ldr r5, [r1]\n.endif\n" + ".if \\k == 2\n ldr r6, [r0, #12]\n.endif\n" + ".if \\k == 3\n add lr, r5, lr, asr r6\n.endif\n" + ".if \\k == 4\n str lr, [r1]\n.endif\n" + ".if \\k == 5\n ldr r1, [r0]\n.endif\n" + ".if \\k == 6\n add r1, r1, #\\step\n.endif\n" + ".if \\k == 7\n ands r1, r1, #63\n.endif\n" + ".if \\k == 8\n stmia r0, { r1, r5 }\n.endif\n" + ".endif\n" + ".endr\n" + ".if \\stage >= 7\n" + " mov r4, #1\n" + " orr r5, r4, r9, asr #30\n" + " orr r6, r4, r10, asr #30\n" + " orr r7, r4, r11, asr #30\n" + " orr r8, r4, r12, asr #30\n" + " mov r6, r6, lsl #1\n" + " mov r7, r7, lsl #1\n" + " mov r8, r8, lsl #2\n" + " sub r12, lr, r12\n" + " sub r11, r12, r11\n" + " sub r10, r11, r10\n" + ".endif\n" + ".if \\stage >= 8\n" + " stmneda r2, { r10, r11, r12, lr }\n" + " stmneda r3, { r5, r6, r7, r8 }\n" + " ldmnefd sp!, { r4-r12, pc }\n" + " add r2, r0, #212\n" + " ldmia r2, { r1, r3, r4 }\n" + " sub r2, r2, #64\n" + " stmia r2, { r1, r3, r4, r9-r12, lr }\n" + " add r9, r0, #116\n" + " ldmia r9, { r1, r2, r3, r4 }\n" + " sub r9, r9, #64\n" + " stmia r9, { r1-r8 }\n" + ".endif\n" + "9:\n" + " ldmfd sp!, { r4-r12, pc }\n" + ".endm\n" + "TCF tcf_0, 0\nTCF tcf_1, 1\nTCF tcf_2, 2\nTCF tcf_3, 3\n" + "TCF tcf_4, 4\nTCF tcf_5, 5\nTCF tcf_6, 6\nTCF tcf_7, 7\n" + "TCF tcf_8, 8\n" + "TCF tcf_61, 5, 1\nTCF tcf_62, 5, 2\nTCF tcf_63, 5, 3\n" + "TCF tcf_64, 5, 4\nTCF tcf_65, 5, 5\nTCF tcf_66, 5, 6\n" + "TCF tcf_67, 5, 7\nTCF tcf_6s, 6, 0, 0\nTCF tcf_7s, 7, 0, 0\n" + /* Stage 5 again at each word of a cache line, and frames holding + only adds, as long as the filter's stages are. */ + ".irp k, 0, 1, 2, 3, 4, 5, 6, 7\n" + ".balign 32\n" + ".if \\k\n" + ".space 4 * \\k\n" + ".endif\n" + "TCF tcf_5c\\k, 5\n" + ".endr\n" + ".macro TCP name, n\n" + ".type \\name, %function\n" + "\\name:\n" + " stmdb sp!, { r4-r12, lr }\n" + ".rept \\n\n" + " add r4, r4, #1\n" + ".endr\n" + " ldmfd sp!, { r4-r12, pc }\n" + ".endm\n" + "TCP tcp_40, 40\nTCP tcp_60, 60\nTCP tcp_70, 70\n" + "TCP tcp_80, 80\nTCP tcp_100, 100\nTCP tcp_120, 120\n" + ".type tcf_ret, %function\n" + "tcf_ret:\n" + " bx lr\n"); +int tcf_5c0(int *, int *); int tcf_5c1(int *, int *); +int tcf_5c2(int *, int *); int tcf_5c3(int *, int *); +int tcf_5c4(int *, int *); int tcf_5c5(int *, int *); +int tcf_5c6(int *, int *); int tcf_5c7(int *, int *); +int tcp_40(int *, int *); int tcp_60(int *, int *); int tcp_70(int *, int *); +int tcp_80(int *, int *); int tcp_100(int *, int *); +int tcp_120(int *, int *); +typedef int (*tcf_fn)(int *fs, int *in); +int tcf_0(int *, int *); int tcf_1(int *, int *); int tcf_2(int *, int *); +int tcf_3(int *, int *); int tcf_4(int *, int *); int tcf_5(int *, int *); +int tcf_6(int *, int *); int tcf_7(int *, int *); int tcf_8(int *, int *); +int tcf_61(int *, int *); int tcf_62(int *, int *); int tcf_63(int *, int *); +int tcf_64(int *, int *); int tcf_65(int *, int *); int tcf_66(int *, int *); +int tcf_67(int *, int *); int tcf_6s(int *, int *); int tcf_7s(int *, int *); +int tcf_ret(int *, int *); + +/* The filter's state, as fltst lays it out: index, error, round, shift, + a spare word, then qm[8], dx[24] and dl[24]. */ +static int tcf_buf[61 + 8] __attribute__((aligned(32))); +static int *tcf_fs = tcf_buf; /* the state, somewhere in tcf_buf */ +static int tcf_in[64]; +static tcf_fn tcf_now; + +static void tcf_loop(unsigned long *unused, unsigned long n) +{ + tcf_fn fn = tcf_now; + int *fs = tcf_fs; + unsigned long i; + int v; + (void)unused; + for (i = 0; i < n; i++) + { + v = tcf_in[i & 63]; + fn(fs, &v); + } +} + +/* Hundredths of a cycle a call, the function's own return included. */ +static long long tcf_cycles(tcf_fn fn) +{ + int i; + rb->memset(tcf_buf, 0, sizeof(tcf_buf)); + tcf_fs[1] = 1; /* error > 0 */ + tcf_fs[2] = 512; /* round */ + tcf_fs[3] = 10; /* shift */ + for (i = 0; i < 64; i++) + tcf_in[i] = 500 + i * 37 % 1000; + tcf_now = fn; + return cpi(tcf_loop, NULL); +} + +static void say_filter_rows(void) +{ + /* By the rows above: the frame 24, then 13, 16, 12, 16, 12, 12 and 11, + and 9.75 for the last, the delay lines' shift included. On an + ARM926 the later stages come out a tenth over, which is the cycle a + line that "adds in a loop" measures: with its caller the function + is more than eight lines of code. The archive has stage 6 an + instruction at a time (6.1 to 6.7, 6 being all eight), and stages + 6 and 7 with the index left where it is (6s, 7s). */ + static const struct { const char *name; tcf_fn fn; int want; } T[] = { + { "0 ", tcf_0, 2400 }, { "1 ", tcf_1, 3700 }, + { "2 ", tcf_2, 5300 }, { "3 ", tcf_3, 6500 }, + { "4 ", tcf_4, 8100 }, { "5 ", tcf_5, 9300 }, +#if TEST_CYC_ARCHIVE + { "6.1", tcf_61, 9400 }, { "6.2", tcf_62, 9500 }, + { "6.3", tcf_63, 9800 }, { "6.4", tcf_64, 9900 }, + { "6.5", tcf_65, 10000 }, { "6.6", tcf_66, 10200 }, + { "6.7", tcf_67, 10300 }, +#endif + { "6 ", tcf_6, 10500 }, + { "7 ", tcf_7, 11600 }, { "8 ", tcf_8, 12575 }, +#if TEST_CYC_ARCHIVE + { "6s ", tcf_6s, 10500 }, { "7s ", tcf_7s, 11600 }, +#endif + }; + long long base = tcf_cycles(tcf_ret); + unsigned k; + int last = 0; + say("tta filter, state at %08lx", (unsigned long)tcf_fs); + say(" stage cyc step by rows"); + for (k = 0; k < sizeof(T) / sizeof(T[0]); k++) + { + char a[8], b[8], c[8]; + int v = (int)(tcf_cycles(T[k].fn) - base) + 300; + fmt(a, v); + fmt(b, v - last); + fmt(c, T[k].want); + say(" %s %s %s %s", T[k].name, a, b, c); + last = v; + } +} + +#if TEST_CYC_ARCHIVE +/* Where the filter's cost depends on something other than its + instructions: stage 5 with its state at each word of a cache line, the + same code at each word of a line, and frames of nothing but adds. */ +static void say_filter_layout(void) +{ + static const tcf_fn code[] = { tcf_5c0, tcf_5c1, tcf_5c2, tcf_5c3, + tcf_5c4, tcf_5c5, tcf_5c6, tcf_5c7 }; + static const struct { tcf_fn fn; int n; } pad[] = { + { tcp_40, 40 }, { tcp_60, 60 }, { tcp_70, 70 }, { tcp_80, 80 }, + { tcp_100, 100 }, { tcp_120, 120 } }; + long long base = tcf_cycles(tcf_ret); + unsigned k; + char a[8], b[8]; + say("stage 5 (93) and 4 (81), state at word"); + for (k = 0; k < 8; k++) + { + tcf_fs = tcf_buf + k; + fmt(a, (int)(tcf_cycles(tcf_5) - base) + 300); + fmt(b, (int)(tcf_cycles(tcf_4) - base) + 300); + say(" %u %s %s", k, a, b); + } + tcf_fs = tcf_buf; + say("stage 5 (93), code at word"); + for (k = 0; k < 8; k++) + { + fmt(a, (int)(tcf_cycles(code[k]) - base) + 300); + say(" %u %s at %08lx", k, a, (unsigned long)code[k]); + } + say("frame of adds cyc by rows"); + for (k = 0; k < sizeof(pad) / sizeof(pad[0]); k++) + { + fmt(a, (int)(tcf_cycles(pad[k].fn) - base) + 300); + fmt(b, (24 + pad[k].n) * 100); + say(" %3d adds %s %s", pad[k].n, a, b); + } +} +#endif + +/* The empty loop, by where its code and its data are. */ +static long long e_d_d; +#ifdef USE_IRAM +static long long e_i_i, e_i_d, e_d_i; +#endif + +#ifndef TICK_TIMED +#if TEST_CYC_ARCHIVE +/* Which lines a miss evicts. The 16 KB at the start of the buffer is + read until the data cache holds it, then a stream of misses is run + through memory elsewhere, a load a line, and the first 16 KB is read + once more and timed: what share of its lines are still there. The + stream's loop is padded with 0 to 5 adds, since a victim chosen by a + free-running counter would depend on the loop's length where a random + one would not: at random, a quarter of what is left goes with every 128 + lines, leaving 75%, 32%, and after 2048 lines nothing. */ +#define EVICT(name, pad) \ +static void name(unsigned long *p, unsigned long n) \ +{ \ + asm volatile ("1:\n" \ + " ldr r4, [%[p]], #32\n" \ + pad \ + " subs %[n], %[n], #1\n" \ + " bne 1b\n" \ + : [n] "+&r" (n), [p] "+&r" (p) : : "r4", "r5", "cc"); \ +} +#define PAD1 " add r5, r5, #1\n" +EVICT(evict_0, "") +EVICT(evict_1, PAD1) +EVICT(evict_2, PAD1 PAD1) +EVICT(evict_3, PAD1 PAD1 PAD1) +EVICT(evict_5, PAD1 PAD1 PAD1 PAD1 PAD1) + +/* Timer counts for one read of the first 16 KB, after `lines` misses made + by fn elsewhere; fn NULL leaves it as it is. */ +static unsigned long evict_pass(unsigned char *big, benchfn fn, + unsigned long lines) +{ + unsigned long t; + int i; + for (i = 0; i < 12; i++) + evict_0((unsigned long *)big, 512); + if (fn) + fn((unsigned long *)(big + 65536), lines); + t = NOW(); + evict_0((unsigned long *)big, 512); + return NOW() - t; +} + +/* Which way a miss takes, miss after miss. Four lines are loaded into + one set until they fill it; then some new lines are loaded into that + set, with misses in other sets in between; then the four are read again + and timed, each followed by enough adds for a miss to cost its stall + alone. A victim picked at random each time keeps 75%, 56%, 42% and 32% + of the four after 1 to 4 new lines whatever comes between; one that + takes the ways in turn keeps 75%, 50%, 25% and none. */ +void evict_reload(unsigned char *line0); +__asm__(".text\n.align 2\n.type evict_reload, %function\n" + "evict_reload:\n" + " mov r2, #4\n" + "1:\n" + " ldr r1, [r0]\n" + " add r0, r0, #4096\n" + " .rept 32\n" + " add r3, r3, #1\n" + " .endr\n" + " subs r2, r2, #1\n" + " bne 1b\n" + " bx lr\n"); + +static unsigned long evict_set_trial(unsigned char *big, int news, int gap) +{ + unsigned long total = 0, t, fill = 0; + int set, k, round, i; + for (set = 0; set < 128; set++) + { + unsigned char *line = big + set * 32; + for (round = 0; round < 8; round++) + for (k = 0; k < 4; k++) + (void)*(volatile unsigned long *)(line + k * 4096); + for (k = 0; k < news; k++) + { + (void)*(volatile unsigned long *)(line + (4 + k) * 4096); + for (i = 0; i < gap; i++, fill++) + (void)*(volatile unsigned long *)(big + 131072 + + ((set + 1 + fill % 126) & 127) * 32 + + (fill / 126 % 16) * 4096); + } + t = NOW(); + evict_reload(line); + total += NOW() - t; + } + return total; +} + +static void say_evict_set_rows(void) +{ + static const unsigned char gaps[] = { 0, 1, 2, 3, 7, 127 }; + size_t bufsize; + unsigned char *big = rb->plugin_get_buffer(&bufsize); + unsigned long hit = 0, miss = 0; + unsigned g, n, r; + big = (unsigned char *)(((unsigned long)big + 31) & ~31ul); + if (bufsize < 262144) + return; + for (r = 0; r < 8; r++) + { + hit += evict_set_trial(big, 0, 0); + miss += evict_set_trial(big, 20, 0); + } + say("4 lines of a set: kept %lu, gone %lu counts", hit / 8, miss / 8); + say("kept, %%, after new lines in the set"); + say(" between 1 2 3 4 8"); + for (g = 0; g < sizeof(gaps); g++) + { + static const unsigned char news[] = { 1, 2, 3, 4, 8 }; + int kept[5]; + for (n = 0; n < 5; n++) + { + unsigned long t = 0; + for (r = 0; r < 8; r++) + t += evict_set_trial(big, news[n], gaps[g]); + kept[n] = miss > hit + ? (int)(100 - 100 * ((long long)t - hit) / (miss - hit)) : 0; + } + say(" %3d %4d %4d %4d %4d %4d", gaps[g], kept[0], kept[1], + kept[2], kept[3], kept[4]); + } +} +#endif + +/* The same question asked more sharply: a line is loaded, then one to + five more into its set, each a miss with the bus idle before it (32 + adds, plus 0 to 7 more to move the misses in time), and the first is + read again. Chosen at random it survives 75%, 56%, 42%, 32% and 24% of + the time, whatever the spacing. With the ways taken in turn it always + survives three more and never four. With the victim read off a + free-running counter it depends on the spacing, a row at a time. */ +#define EVICTN(name, pad) \ +void name(unsigned char *line, unsigned long n); +#define EVICTN_ASM(name, pad) \ + ".type " #name ", %function\n" \ + #name ":\n" \ + "1:\n" \ + " ldr r2, [r0]\n" \ + " add r0, r0, #4096\n" \ + " .rept 32 + " #pad "\n" \ + " add r3, r3, #1\n" \ + " .endr\n" \ + " subs r1, r1, #1\n" \ + " bne 1b\n" \ + " bx lr\n" +EVICTN(evictn_0, 0) EVICTN(evictn_1, 1) EVICTN(evictn_2, 2) +EVICTN(evictn_3, 3) EVICTN(evictn_4, 4) EVICTN(evictn_5, 5) +EVICTN(evictn_6, 6) EVICTN(evictn_7, 7) +__asm__(".text\n.align 2\n" + EVICTN_ASM(evictn_0, 0) EVICTN_ASM(evictn_1, 1) + EVICTN_ASM(evictn_2, 2) EVICTN_ASM(evictn_3, 3) + EVICTN_ASM(evictn_4, 4) EVICTN_ASM(evictn_5, 5) + EVICTN_ASM(evictn_6, 6) EVICTN_ASM(evictn_7, 7)); + +/* Timer counts to read back the first of 1 + more lines loaded into each + set in turn. The lines come from 56 that share the set, taken round, + so that nearly every load is a miss. */ +static unsigned long evict_lag_trial(unsigned char *big, + void (*fn)(unsigned char *, + unsigned long), + int more) +{ + static unsigned tag; + unsigned long total = 0, t; + int set; + for (set = 0; set < 128; set++) + { + unsigned char *line; + if (tag + 1 + more > 56) + tag = 0; + line = big + set * 32 + tag * 4096; + fn(line, 1 + more); + t = NOW(); + evictn_0(line, 1); + total += NOW() - t; + tag += 1 + more; + } + return total; +} + +static void say_evict_lag_rows(void) +{ + static void (*const fn[])(unsigned char *, unsigned long) = { + evictn_0, evictn_1, evictn_2, evictn_3, + evictn_4, evictn_5, evictn_6, evictn_7 }; + size_t bufsize; + unsigned char *big = rb->plugin_get_buffer(&bufsize); + unsigned long hit = 0, miss = 0; + unsigned d, k, r; + big = (unsigned char *)(((unsigned long)big + 31) & ~31ul); + if (bufsize < 56 * 4096 + 8192) + return; + for (r = 0; r < 16; r++) + { + hit += evict_lag_trial(big, evictn_0, 0); + miss += evict_lag_trial(big, evictn_0, 16); + } + say("first line back: kept %lu, gone %lu counts", hit / 16, miss / 16); + say("first line kept, %%, after more misses in its set"); + say(" pad 1 2 3 4 5"); + for (d = 0; d < 8; d++) + { + int kept[5]; + for (k = 0; k < 5; k++) + { + unsigned long t = 0; + for (r = 0; r < 16; r++) + t += evict_lag_trial(big, fn[d], k + 1); + kept[k] = miss > hit + ? (int)(100 - 100 * ((long long)t - hit) / (miss - hit)) : 0; + } + say(" %u %4d %4d %4d %4d %4d", d, kept[0], kept[1], kept[2], + kept[3], kept[4]); + } +} + +#if TEST_CYC_ARCHIVE +static void say_evict_rows(void) +{ + static const struct { benchfn fn; int pad; } E[] = { + { evict_0, 0 }, { evict_1, 1 }, { evict_2, 2 }, { evict_3, 3 }, + { evict_5, 5 } }; + static const unsigned short lines[] = { 128, 512, 2048 }; + size_t bufsize; + unsigned char *big = rb->plugin_get_buffer(&bufsize); + unsigned long hit = 0, miss = 0, t; + unsigned k, j, r; + big = (unsigned char *)(((unsigned long)big + 31) & ~31ul); + if (bufsize < 65536 + 2048 * 32 + 64) + return; + for (r = 0; r < 16; r++) + { + hit += evict_pass(big, NULL, 0); + miss += evict_pass(big, evict_0, 4096); + } + say("16 KB read: cached %lu, evicted %lu counts", hit / 16, miss / 16); + say("lines kept, %%, after misses elsewhere"); + say(" pad 128 512 2048 lines"); + for (k = 0; k < sizeof(E) / sizeof(E[0]); k++) + { + int kept[3]; + for (j = 0; j < 3; j++) + { + t = 0; + for (r = 0; r < 16; r++) + t += evict_pass(big, E[k].fn, lines[j]); + kept[j] = miss > hit + ? (int)(100 - 100 * ((long long)t - hit) / (miss - hit)) : 0; + } + say(" %d %4d %4d %4d", E[k].pad, kept[0], kept[1], kept[2]); + } +} +#endif +#endif + +/* Misses with room after them, and back to back, in the memory at big: + the plugin buffer, which is DRAM, or on an AS3525v2 the megabyte of RAM + inside the SoC, where codecs are loaded with all their data. */ +static void say_miss_gap_rows(const char *where, unsigned char *big, + size_t bufsize) +{ + static const struct { const char *name; benchfn fn; } G[] = { + { "+32 alu ", gap_32 }, +#if TEST_CYC_ARCHIVE + { "+64 alu ", gap_64 }, +#endif + { "+128 alu ", gap_128 }, +#if TEST_CYC_ARCHIVE + { "+256 alu ", gap_256 }, +#endif + { "ldr w1, +32 ", gap_w1 }, + { "ldr w7, +32 ", gap_w7 }, +#if TEST_CYC_ARCHIVE + { "ldm 2, +32 ", gap_ldm2 }, +#endif + { "ldm 4, +32 ", gap_ldm4 }, + { "ldm w2-5,+32", gap_ldm4mid }, + { "str w0, +32 ", gap_str }, + }; + static const struct { const char *name; benchfn fn; } P[] = { + { "line, no gap", stream_line }, + { "ldm 4 stream", stream_ldm4 }, + { "dirty 4 ", stream_dirty4 }, + { "dirty 8 ", stream_dirty8 }, + { "str stream ", wstream_word }, + { "str a line ", wstream_line }, + { "stm 4 stream", wstream_stm4 }, + { "copy 4 words", copy_4 }, + }; + unsigned long large; + unsigned k; + char a[8], b[8], c[8]; + int hit, miss; + large = (bufsize > 262144 ? 262144 : bufsize) & ~511ul; + say("%s at %08lx", where, (unsigned long)big); + say("miss, then cached %luKB extra", large / 1024); + for (k = 0; k < sizeof(G) / sizeof(G[0]); k++) + { + sbuf = (unsigned long *)big; + sbytes = 1024; + hit = per_insn(cpi_from(G[k].fn, NULL, 2048), e_d_d); + sbytes = large; + miss = per_insn(cpi_from(G[k].fn, NULL, 2048), e_d_d); + fmt(a, hit); + fmt(b, miss); + fmt(c, miss - hit); + say(" %s %s %s %s", G[k].name, a, b, c); + } + if (bufsize >= 262144) + { + make_chain(big, 1024, 32); + hit = per_insn(cpi_from(chase_work32, (unsigned long *)big, 2048), + e_d_d); + make_chain(big, 262144, 32); + miss = per_insn(cpi_from(chase_work32, (unsigned long *)big, 2048), + e_d_d); + fmt(a, hit); + fmt(b, miss); + fmt(c, miss - hit); + say(" chase, +32 %s %s %s", a, b, c); + make_chain_scattered(big, 262144, 32); + miss = per_insn(cpi_from(chase_work32, (unsigned long *)big, 2048), + e_d_d); + fmt(b, miss); + fmt(c, miss - hit); + say(" scattered %s %s %s", a, b, c); + } + /* Back to back. The first kilobyte is read before each row, so the + store rows' first column is of stores to lines that are cached. */ + for (k = 0; k < sizeof(P) / sizeof(P[0]); k++) + { + sbuf = (unsigned long *)big; + sbytes = 1024; + stream_word(NULL, 64); + hit = per_insn(cpi_from(P[k].fn, NULL, 2048), e_d_d); + /* The copy reads the first half of the buffer and writes the + second. */ + sbytes = P[k].fn == copy_4 ? 131072 : large; + miss = per_insn(cpi_from(P[k].fn, NULL, 2048), e_d_d); + fmt(a, hit); + fmt(b, miss); + fmt(c, miss - hit); + say(" %s %s %s %s", P[k].name, a, b, c); + } +} + +static void say_miss_rows_everywhere(void) +{ + size_t bufsize; + unsigned char *big = rb->plugin_get_buffer(&bufsize); + big = (unsigned char *)(((unsigned long)big + 31) & ~31ul); + say_miss_gap_rows("DRAM", big, bufsize - 32); +#if CONFIG_CPU == AS3525v2 + /* The SoC's own RAM is mapped after the DRAM; its first 32 KB is the + firmware's and the rest is where a codec goes, free while nothing + plays. */ + rb->audio_stop(); + say_miss_gap_rows("SoC RAM", (unsigned char *)0x30000000 + + MEMORYSIZE * 0x100000 + 0x40000, 0x80000); +#endif +} + +#if ARM_ARCH >= 5 +/* Fetch misses in any memory: the code is written where it is to run. A + loop of one taken branch a line, or of lines of eight adds, over 4 KB, + which the cache holds, and over 64 KB, where every line is a miss. */ +static void make_code(unsigned long *code, int lines, bool adds) +{ + unsigned long *w = code; + int k, j; + for (k = 0; k < lines; k++, w += 8) + { + for (j = 0; j < 8; j++) + w[j] = 0xe2800001; /* add r0, r0, #1 */ + if (!adds) + w[0] = 0xea000006; /* b the next line */ + } + w[0] = 0xe2511001; /* subs r1, r1, #1 */ + w[1] = 0x1a000000 /* bne the first line */ + | ((unsigned long)(code - (w + 1) - 2) & 0xfffffful); + w[2] = 0xe12fff1e; /* bx lr */ + rb->commit_dcache(); + rb->commit_discard_idcache(); +} + +static void say_code_miss_rows(const char *where, unsigned long *code) +{ + static const char *const name[] = { "a branch a line", "8 adds a line " }; + int v; + char a[8], b[8], c[8]; + say("%s at %08lx, code", where, (unsigned long)code); + say("cycles a line 4KB 64KB extra"); + for (v = 0; v < 2; v++) + { + int hit, miss; + make_code(code, 128, v); + hit = (int)(cpi_from((benchfn)code, NULL, 512) / 128); + make_code(code, 2048, v); + miss = (int)(cpi_from((benchfn)code, NULL, 64) / 2048); + fmt(a, hit); + fmt(b, miss); + fmt(c, miss - hit); + say(" %s %s %s %s", name[v], a, b, c); + } +} + +#if CONFIG_CPU == AS3525v2 +/* The copy of copy_4, from one memory to the other: whether a fill in one + waits for the writes still to go out to the other. */ +static unsigned long sdelta; +static void copy_across(unsigned long *unused, unsigned long n) +{ + unsigned long *p = sbuf, *end = sbuf + sbytes / 4; + (void)unused; + asm volatile ("1:\n" + X16(" ldmia %[p], { r4-r7 }\n" + " add r8, %[p], %[d]\n" + " stmia r8, { r4-r7 }\n" + " add %[p], %[p], #16\n") + " cmp %[p], %[end]\n" + " movhs %[p], %[base]\n" + " subs %[n], %[n], #1\n" + " bne 1b\n" + : [n] "+&r" (n), [p] "+&r" (p) + : [end] "r" (end), [base] "r" (sbuf), [d] "r" (sdelta) + : "r4", "r5", "r6", "r7", "r8", "cc", "memory"); +} + +static void say_copy_across(const char *name, unsigned long *from, + unsigned long *to) +{ + char a[8], b[8], c[8]; + int hit, miss; + sbuf = from; + sdelta = (unsigned long)to - (unsigned long)from; + sbytes = 1024; + stream_word(NULL, 64); + hit = per_insn(cpi_from(copy_across, NULL, 2048), e_d_d); + sbytes = 131072; + miss = per_insn(cpi_from(copy_across, NULL, 2048), e_d_d); + fmt(a, hit); + fmt(b, miss); + fmt(c, miss - hit); + say(" %s %s %s %s", name, a, b, c); +} +#endif + +/* Tremor's window loop, which reads two buffers, one upwards and one + downwards, and stores a word to each of two more, none of them cached: + args is the buffer, a quarter of its size and a small table. And the + two streams of stores alone. Each pass is 16 times the body. */ +#define VBODY_STORES \ + " str r5, [r9, #-4]!\n" \ + " str r5, [r6], #4\n" +#define VBODY_WINDOW \ + " ldr r0, [r7], #4\n" \ + " ldr r1, [ip, #-4]!\n" \ + " ldr r2, [r8]\n" \ + " ldr r3, [r8, #4]\n" \ + " smull r4, r5, r0, r2\n" \ + " smlal r4, r5, r1, r3\n" \ + " rsb r2, r2, #0\n" \ + " mov r5, r5, lsl #1\n" \ + " str r5, [r9, #-4]!\n" \ + " smull r4, r5, r0, r3\n" \ + " smlal r4, r5, r1, r2\n" \ + " mov r5, r5, lsl #1\n" \ + " str r5, [r6], #4\n" +#define VLOOP(name, body, step) \ +void name(unsigned long *args, unsigned long n); \ +asm (" .pushsection .text." #name ", \"ax\", %progbits\n" \ + " .align 2\n" \ + " .type " #name ", %function\n" \ + #name ":\n" \ + " stmfd sp!, { r4-r11, lr }\n" \ + " mov r11, r0\n" \ + " mov r10, r1\n" \ + " ldr r8, [r11, #8]\n" \ + "0:\n" \ + " ldr r7, [r11]\n" \ + " ldr r3, [r11, #4]\n" \ + " add ip, r7, r3, lsl #1\n" \ + " add r6, ip, r3\n" \ + " mov r9, r6\n" \ + " add lr, r7, r3\n" \ + "1:\n" \ + X16(body) \ + step \ + " subs r10, r10, #1\n" \ + " beq 2f\n" \ + " cmp r7, lr\n" \ + " blo 1b\n" \ + " b 0b\n" \ + "2:\n" \ + " ldmfd sp!, { r4-r11, pc }\n" \ + " .popsection\n"); +VLOOP(vloop_stores, VBODY_STORES, " add r7, r7, #64\n") +VLOOP(vloop_window, VBODY_WINDOW, "") + +static void say_window_rows(const char *where, unsigned long *big, + size_t bufsize) +{ + static const struct { const char *name; benchfn fn; } V[] = { + { "2 str streams", vloop_stores }, + { "window loop ", vloop_window }, + }; + static unsigned long table[2] = { 0x3fffffff, 0x12345678 }; + unsigned long args[3]; + unsigned long large = (bufsize > 262144 ? 262144 : bufsize) & ~1023ul; + unsigned k; + char a[8], b[8], c[8]; + say("%s at %08lx", where, (unsigned long)big); + say("two streams out cached %luKB extra", large / 1024); + args[0] = (unsigned long)big; + args[2] = (unsigned long)table; + for (k = 0; k < sizeof(V) / sizeof(V[0]); k++) + { + int hit, miss; + sbuf = big; + sbytes = 1024; + stream_word(NULL, 64); + args[1] = 256; + hit = per_insn(cpi_from(V[k].fn, args, 2048), e_d_d); + args[1] = large / 4; + miss = per_insn(cpi_from(V[k].fn, args, 2048), e_d_d); + fmt(a, hit); + fmt(b, miss); + fmt(c, miss - hit); + say(" %s %s %s %s", V[k].name, a, b, c); + } +} + +static void say_window_rows_everywhere(void) +{ + size_t bufsize; + unsigned char *big = rb->plugin_get_buffer(&bufsize); + big = (unsigned char *)(((unsigned long)big + 31) & ~31ul); + say_window_rows("DRAM", (unsigned long *)big, bufsize - 32); +#if CONFIG_CPU == AS3525v2 + rb->audio_stop(); + say_window_rows("SoC RAM", (unsigned long *)(0x30000000 + + MEMORYSIZE * 0x100000 + 0x40000), 0x80000); +#endif +} + +static void say_code_miss_rows_everywhere(void) +{ + size_t bufsize; + unsigned char *big = rb->plugin_get_buffer(&bufsize); + big = (unsigned char *)(((unsigned long)big + 31) & ~31ul); + if (bufsize >= 0x10100) + say_code_miss_rows("DRAM", (unsigned long *)big); +#if CONFIG_CPU == AS3525v2 + rb->audio_stop(); + say_code_miss_rows("SoC RAM", (unsigned long *)(0x30000000 + + MEMORYSIZE * 0x100000 + 0x40000)); + if (bufsize >= 0x20100) + { + unsigned long *soc = (unsigned long *)(0x30000000 + + MEMORYSIZE * 0x100000 + 0x40000); + say("copy 4 words cached 128KB extra"); + say_copy_across("DRAM to SoC ", (unsigned long *)big, soc); + say_copy_across("SoC to DRAM ", soc, (unsigned long *)big); + } +#endif +} +#endif + +/* How much code a loop holds against what its instructions cost: loops of + adds filling one cache line to 256 of them, and of one taken branch a + line. */ +static void say_code_size_rows(void) +{ + static const struct { benchfn fn; int n; } S[] = { + { seq_6, 6 }, { seq_14, 14 }, { seq_22, 22 }, { seq_30, 30 }, + { seq_38, 38 }, { seq_46, 46 }, { seq_54, 54 }, { seq_62, 62 }, + { seq_70, 70 }, { seq_78, 78 }, { seq_94, 94 }, + { seq_126, 126 }, { seq_254, 254 }, { seq_510, 510 }, + { seq_1022, 1022 }, { seq_2046, 2046 } }; + static const struct { benchfn fn; int n; } C[] = { + { ichain_1, 1 }, { ichain_2, 2 }, { ichain_3, 3 }, { ichain_4, 4 }, + { ichain_5, 5 }, { ichain_6, 6 }, { ichain_7, 7 }, { ichain_8, 8 }, + { ichain_9, 9 }, { ichain_10, 10 }, { ichain_12, 12 }, + { ichain_16, 16 }, { ichain_small, 32 }, + { ichain_64, 64 }, { ichain_128, 128 }, { ichain_256, 256 } }; + unsigned k; + char a[8], b[8]; + say("adds in a loop lines cyc/add over"); + for (k = 0; k < sizeof(S) / sizeof(S[0]); k++) + { + long long c = cpi_from(S[k].fn, NULL, 3200000ul / S[k].n) - e_d_d; + fmt(a, (int)(c / S[k].n)); + fmt(b, (int)(c - S[k].n * 100)); + say(" %4d adds %3d %s %s", S[k].n, (S[k].n + 2) / 8, a, b); + } + say("a branch a line cyc/branch"); + for (k = 0; k < sizeof(C) / sizeof(C[0]); k++) + { + long long c = cpi_from(C[k].fn, NULL, 1600000ul / C[k].n) - e_d_d; + fmt(a, (int)(c / C[k].n)); + say(" %3d lines %s", C[k].n, a); + } + /* The data side: the pointer chase round a few lines, all cached. */ + { + static const unsigned char lines[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 14, 16, 24, 32 }; + size_t bufsize; + unsigned char *big = rb->plugin_get_buffer(&bufsize); + big = (unsigned char *)(((unsigned long)big + 31) & ~31ul); + say("chase, cached cyc/load"); + for (k = 0; k < sizeof(lines); k++) + { + make_chain(big, lines[k] * 32, 32); + fmt(a, per_insn(cpi(chase, (unsigned long *)big), e_d_d)); + say(" %3d lines %s", lines[k], a); + } + } +} + +static void say_rows(const struct row *r, int n) +{ + int k; + for (k = 0; k < n; k++) + { + char d[8]; + int div = r[k].per, sub = r[k].alu * 100; + fmt(d, (per_insn(cpi(r[k].d, dram_buf), e_d_d) - sub) / div); +#ifdef USE_IRAM + { + char a[8], b[8], c[8]; + fmt(a, (per_insn(cpi(r[k].i, iram_buf), e_i_i) - sub) / div); + fmt(b, (per_insn(cpi(r[k].i, dram_buf), e_i_d) - sub) / div); + fmt(c, (per_insn(cpi(r[k].d, iram_buf), e_d_i) - sub) / div); + say("%s %s %s %s %s", r[k].name, a, b, c, d); + } +#else + say("%s %s", r[k].name, d); +#endif + } +} + +/* Define to run only the newest rows, for a quick turn on a device + whose other rows are already known. */ +/* #define TEST_CYC_QUICK */ + +enum plugin_status plugin_start(const void* parameter) +{ + long long loop_cyc; + (void)parameter; + + rb->lcd_setfont(FONT_SYSFIXED); + rb->lcd_clear_display(); + fd = rb->open("/test_cyc.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666); + if (fd < 0) + say("cannot create /test_cyc.txt (%d): screen only", fd); + +#ifdef HAVE_ADJUSTABLE_CPU_FREQ + rb->cpu_boost(true); +#endif + + /* subs and a taken branch: four cycles an iteration by the datasheet. + A different figure here scales every other number the same way. */ + e_d_d = cpi(b_empty_d, dram_buf); +#ifdef USE_IRAM + e_i_i = cpi(b_empty_i, iram_buf); + e_i_d = cpi(b_empty_i, dram_buf); + e_d_i = cpi(b_empty_d, iram_buf); + loop_cyc = e_i_i; +#else + loop_cyc = e_d_d; +#endif + say("ARMv%d, clock %ld MHz, loop %d.%02d cyc (want 4)", ARM_ARCH, + *rb->cpu_frequency / 1000000, (int)(loop_cyc / 100), + (int)(loop_cyc % 100)); + say("timer: %s", TIMER_NAME); +#ifdef USE_IRAM + say("cyc/insn code:IRAM IRAM DRAM DRAM"); + say(" data:IRAM DRAM IRAM DRAM"); +#else + say("no plugin IRAM: code and data in DRAM"); + say("cyc/insn"); +#endif + +#if ARM_ARCH >= 5 && !defined(CPU_ARM_MICRO) + { + /* The control register, whose bit 14 picks round-robin cache + replacement over random, and the cache type register. */ + unsigned long ctrl, ctype; + asm volatile ("mrc p15, 0, %0, c1, c0, 0" : "=r" (ctrl)); + asm volatile ("mrc p15, 0, %0, c0, c0, 1" : "=r" (ctype)); + say("cp15 control %08lx (RR bit %lu), cache type %08lx", ctrl, + (ctrl >> 14) & 1, ctype); + } +#endif + +#ifdef TEST_CYC_QUICK + say("quick run: the newest rows only"); +#if ARM_ARCH >= 5 + say_window_rows_everywhere(); +#else + say_miss_rows_everywhere(); +#endif +#else + say_rows(B, NB); + say_rows(B2, NB2); + say_rows(B3, NB3); + say_filter_rows(); +#if TEST_CYC_ARCHIVE + say_filter_layout(); +#endif + say_code_size_rows(); +#ifndef TICK_TIMED +#if TEST_CYC_ARCHIVE + say_evict_rows(); + say_evict_set_rows(); +#endif + say_evict_lag_rows(); +#endif + say_miss_rows_everywhere(); +#if ARM_ARCH >= 5 + say_code_miss_rows_everywhere(); + say_window_rows_everywhere(); +#endif + + /* Stack traffic, here and on the codec thread. */ +#ifdef USE_IRAM + sp_empty_i = e_i_d; +#endif + sp_empty_d = e_d_d; + stack_rows(); + say_stack_rows("main"); + sp_done = false; + rb->codec_thread_do_callback(stack_rows, NULL); + while (!sp_done) + rb->sleep(HZ / 10); + rb->codec_thread_do_callback(NULL, NULL); + say_stack_rows("codec"); + +#ifdef IRAM_MAP_BYTES + /* What a store costs from IRAM code, by where in IRAM it lands. */ + { + unsigned long off; + say("iram store map, code at %08lx", (unsigned long)b_str_i); + for (off = 0; off < IRAM_MAP_BYTES; off += 4096) + { + char a[8]; + unsigned long *q = &iram_map[off / 4]; + fmt(a, per_insn(cpi(b_str_i, q), e_i_i)); + say(" %08lx %s", (unsigned long)q, a); + } + } +#endif + + /* Misses: a pointer chase over working sets either side of the cache, + in DRAM code, from the plugin buffer. The loop's own cost is the + empty loop's, which the DRAM-code, DRAM-data run measured above. */ + { + static const unsigned long sizes[] = { 1024, 4096, 8192, 16384, + 32768, 65536, 262144 }; + size_t bufsize; + unsigned char *big = rb->plugin_get_buffer(&bufsize); + unsigned long s32, k2; + big = (unsigned char *)(((unsigned long)big + 31) & ~31ul); + bufsize -= 32; + say("addr: iram %08lx dram %08lx chase %08lx", + (unsigned long)iram_buf, (unsigned long)dram_buf, + (unsigned long)big); + say("chase cyc/load stride 16 stride 32"); + for (k2 = 0; k2 < sizeof(sizes) / sizeof(sizes[0]); k2++) + { + char a[8], b[8]; + int v[2]; + unsigned long bytes = sizes[k2]; + if (bytes > bufsize) + break; + for (s32 = 0; s32 < 2; s32++) + { + make_chain(big, bytes, s32 ? 32 : 16); + v[s32] = per_insn(cpi(chase, (unsigned long *)big), e_d_d); + } + fmt(a, v[0]); + fmt(b, v[1]); + say(" %4lu KB %s %s", bytes / 1024, a, b); + } + + /* Streaming: the same plugin buffer, as large as it allows up to + 256 KB, against 1 KB of it. */ + { + static const struct { const char *name; benchfn fn; } S[] = { + { "word ", stream_word }, + { "line 32", stream_line }, + { "ldm 4 ", stream_ldm4 }, + }; + unsigned long large = bufsize > 262144 ? 262144 : bufsize; + large &= ~511ul; + say("stream cyc/load cached %luKB extra", large / 1024); + for (k2 = 0; k2 < sizeof(S) / sizeof(S[0]); k2++) + { + char a[8], b[8], c[8]; + int hit, miss; + sbuf = (unsigned long *)big; + sbytes = 1024; + hit = per_insn(cpi(S[k2].fn, NULL), e_d_d); + sbytes = large; + miss = per_insn(cpi(S[k2].fn, NULL), e_d_d); + fmt(a, hit); + fmt(b, miss); + fmt(c, miss - hit); + say(" %s %s %s %s", S[k2].name, a, b, c); + } + } + + /* Stores: to 1 KB that was read first, so its lines are cached; + to 1 KB after 32 KB elsewhere was read, so they are not, unless + a store brings its line in; and over the large buffer. */ + { + static const struct { const char *name; benchfn fn; } W[] = { + { "word ", wstream_word }, + { "line 32", wstream_line }, + { "stm 4 ", wstream_stm4 }, + }; + unsigned long large = bufsize > 262144 ? 262144 : bufsize; + large &= ~511ul; + say("store stream 1K read 1K cold %luKB", large / 1024); + for (k2 = 0; k2 < sizeof(W) / sizeof(W[0]); k2++) + { + char a[8], b[8], c[8]; + sbuf = (unsigned long *)big; + sbytes = 1024; + stream_word(NULL, 64); + fmt(a, per_insn(cpi(W[k2].fn, NULL), e_d_d)); + if (bufsize >= 131072) + { + sbuf = (unsigned long *)(big + 65536); + sbytes = 32768; + stream_line(NULL, 256); + } + sbuf = (unsigned long *)big; + sbytes = 1024; + fmt(b, per_insn(cpi_from(W[k2].fn, NULL, ITERS), e_d_d)); + sbytes = large; + fmt(c, per_insn(cpi(W[k2].fn, NULL), e_d_d)); + say(" %s %s %s %s", W[k2].name, a, b, c); + } + } + + /* A miss with work after it. */ + { + static const struct { const char *name; benchfn fn; } K[] = { + { "+0 alu ", stream_line }, + { "+8 alu ", stream_work8 }, + { "+16 alu", stream_work16 }, + { "+32 alu", stream_work32 }, + }; + unsigned long large = bufsize > 262144 ? 262144 : bufsize; + large &= ~511ul; + say("load a line cached %luKB extra", large / 1024); + for (k2 = 0; k2 < sizeof(K) / sizeof(K[0]); k2++) + { + char a[8], b[8], c[8]; + int hit, miss; + sbuf = (unsigned long *)big; + sbytes = 1024; + hit = per_insn(cpi(K[k2].fn, NULL), e_d_d); + sbytes = large; + miss = per_insn(cpi(K[k2].fn, NULL), e_d_d); + fmt(a, hit); + fmt(b, miss); + fmt(c, miss - hit); + say(" %s %s %s %s", K[k2].name, a, b, c); + } + } + + /* Which word of the line the miss asks for, and what it costs + when the lines it evicts are dirty. */ + { + static const struct { const char *name; benchfn fn; } O[] = { + { "word 0 ", stream_first }, + { "word 3 ", stream_mid }, + { "word 7 ", stream_last }, +#if TEST_CYC_ARCHIVE + { "word 3+rest", stream_mid_rest }, + { "word 7+rest", stream_last_rest }, + { "w0, dirty 4", stream_dirty4 }, + { "w0, dirty 8", stream_dirty8 }, + { "dirty 8 +16", stream_dirty8_w16 }, +#endif + { "dirty 8 +32", stream_dirty8_w32 }, + { "dirty 8 +64", stream_dirty8_w64 }, + { "dirty 8 +96", stream_dirty8_w96 }, +#if TEST_CYC_ARCHIVE + { "word 7 +16 ", stream_last_w16 }, +#endif + { "word 7 +32 ", stream_last_w32 }, + }; + unsigned long large = bufsize > 262144 ? 262144 : bufsize; + large &= ~511ul; + say("miss on cached %luKB extra", large / 1024); + for (k2 = 0; k2 < sizeof(O) / sizeof(O[0]); k2++) + { + char a[8], b[8], c[8]; + int hit, miss; + sbuf = (unsigned long *)big; + sbytes = 1024; + hit = per_insn(cpi(O[k2].fn, NULL), e_d_d); + sbytes = large; + miss = per_insn(cpi(O[k2].fn, NULL), e_d_d); + fmt(a, hit); + fmt(b, miss); + fmt(c, miss - hit); + say(" %s %s %s %s", O[k2].name, a, b, c); + } + } + +#if TEST_CYC_ARCHIVE + /* Two buffers filtered in turn and then rewritten together: cycles + a pass of the rewrite, by buffer size and how far apart they + are. 512 bytes each stay cached; 16 KB each do not both fit. */ + if (bufsize >= 131072) + { + static const struct { unsigned bytes, apart; } P[] = { + { 512, 32768 }, { 4096, 32768 }, { 8192, 32768 }, + { 16384, 32768 }, { 16384, 34816 }, { 16384, 16384 }, + { 32768, 32768 }, + }; + say("pair rewrite cyc/pass, 2 x 16 bytes"); + for (k2 = 0; k2 < sizeof(P) / sizeof(P[0]); k2++) + { + char a[8]; + long long both, filt; + pair_a = (unsigned long *)big; + pair_b = (unsigned long *)(big + P[k2].apart); + pair_passes = P[k2].bytes / 16; + filt = cpi_from(pair_filters, NULL, 4); + both = cpi_from(pair_frame, NULL, 4); + fmt(a, (int)((both - filt) / pair_passes)); + say(" %2uK, %2uK apart %s", P[k2].bytes / 1024, + P[k2].apart / 1024, a); + } + } +#endif + + /* Loads far apart: the chase again, in scattered order. */ + { + static const unsigned long sizes[] = { 65536, 262144, 1048576 }; + say("chase scattered stride 32"); + for (k2 = 0; k2 < sizeof(sizes) / sizeof(sizes[0]); k2++) + { + char a[8]; + if (sizes[k2] > bufsize) + break; + make_chain_scattered(big, sizes[k2], 32); + fmt(a, per_insn(cpi(chase, (unsigned long *)big), e_d_d)); + say(" %4lu KB %s", sizes[k2] / 1024, a); + } + } + } + + /* Instruction fetch misses: a taken branch per line of DRAM code, over + 32 lines of it and over 2048. */ + { + char a[8], b[8], c[8]; + int small = (int)((cpi_from(ichain_small, NULL, 4096) - e_d_d) + / ICHAIN_SMALL); + int big2 = (int)((cpi_from(ichain_big, NULL, 64) - e_d_d) + / ICHAIN_BIG); + fmt(a, small); + fmt(b, big2); + fmt(c, big2 - small); + say("code chain cyc/branch small %s big %s extra %s", a, b, c); + } +#endif /* TEST_CYC_QUICK */ + +#ifdef HAVE_ADJUSTABLE_CPU_FREQ + rb->cpu_boost(false); +#endif + if (fd >= 0) + { + rb->close(fd); + fd = -1; + say("wrote /test_cyc.txt, at the root of the main drive"); + } + + say("any key to exit"); + rb->button_clear_queue(); + rb->button_get(true); + rb->lcd_setfont(FONT_UI); + return PLUGIN_OK; +} diff --git a/apps/plugins/test_disk.c b/apps/plugins/test_disk.c index d53f641b85..f628195b87 100644 --- a/apps/plugins/test_disk.c +++ b/apps/plugins/test_disk.c @@ -20,11 +20,11 @@ #include "plugin.h" #include "lib/helper.h" +#include "lib/xlcd.h" -#define TESTBASEDIR HOME_DIR "/__TEST__" -#define TEST_FILE TESTBASEDIR "/test_disk.tmp" +#define TESTDIRNAME "__TEST__" #define FRND_SEED 0x78C3 /* arbirary */ #if (CONFIG_STORAGE & STORAGE_MMC) @@ -40,9 +40,23 @@ static size_t audiobuflen; static unsigned short frnd_buffer; static int line = 0; static int max_line = 0; +static int line_height = 0; static int log_fd; static char logfilename[MAX_PATH]; -static const char testbasedir[] = TESTBASEDIR; + +/* The disk under test: HOME_DIR, or where there are several volumes the + one chosen from the menu. Its test directory, test file and log are all + on it, so nothing is written anywhere else. */ +#ifdef HAVE_MULTIVOLUME +#define MAX_DISKS (NUM_VOLUMES + 1) +#else +#define MAX_DISKS 1 +#endif +static char disks[MAX_DISKS][24]; +static int ndisks; +static int disk; +static char testbasedir[MAX_PATH]; +static char testfile[MAX_PATH]; static void mem_fill_frnd(unsigned char *addr, int len) { @@ -74,28 +88,31 @@ static bool mem_cmp_frnd(unsigned char *addr, int len) static bool log_init(void) { - int h; - - rb->lcd_getstringsize("A", NULL, &h); - max_line = LCD_HEIGHT / h; + rb->lcd_getstringsize("A", NULL, &line_height); + max_line = LCD_HEIGHT / line_height; line = 0; rb->lcd_clear_display(); rb->lcd_update(); - rb->create_numbered_filename(logfilename, HOME_DIR, "test_disk_log_", ".txt", - 2 IF_CNFN_NUM_(, NULL)); + rb->create_numbered_filename(logfilename, disks[disk], "test_disk_log_", + ".txt", 2 IF_CNFN_NUM_(, NULL)); log_fd = rb->open(logfilename, O_RDWR|O_CREAT|O_TRUNC, 0666); return log_fd >= 0; } static void log_text(char *text, bool advance) { + if (line >= max_line) + { + /* The screen is full: scroll up to make room for this line */ + xlcd_scroll_up(line_height); + line = max_line - 1; + } rb->lcd_puts(0, line, text); rb->lcd_update(); if (advance) { - if (++line >= max_line) - line = 0; + line++; rb->fdprintf(log_fd, "%s\n", text); } } @@ -105,6 +122,84 @@ static void log_close(void) rb->close(log_fd); } +/* The name of the i'th of the many small files the speed test makes. */ +static void tmp_name(char *buf, size_t size, int i) +{ + rb->snprintf(buf, size, "%s/%08x.tmp", testbasedir, i); +} + +/* The disks there are to test: HOME_DIR, and each volume the root lists. */ +static void find_disks(void) +{ + rb->strlcpy(disks[0], HOME_DIR, sizeof disks[0]); + ndisks = 1; +#ifdef HAVE_MULTIVOLUME + { + DIR *dir = rb->opendir(PATH_ROOTSTR); + struct dirent *entry; + + if (dir == NULL) + return; + while (ndisks < MAX_DISKS && (entry = rb->readdir(dir)) != NULL) + { + if (!(rb->dir_get_info(dir, entry).attribute & ATTR_VOLUME)) + continue; + rb->snprintf(disks[ndisks], sizeof disks[0], PATH_ROOTSTR "%s", + entry->d_name); + ndisks++; + } + rb->closedir(dir); + } +#endif +} + +/* Make disk n the one under test. The test directory on the one before + is removed and one made here. */ +static bool use_disk(int n) +{ + DIR *dir; + + if (testbasedir[0]) + rb->rmdir(testbasedir); + disk = n; + rb->snprintf(testbasedir, sizeof testbasedir, "%s/" TESTDIRNAME, + disks[disk]); + rb->snprintf(testfile, sizeof testfile, "%s/test_disk.tmp", testbasedir); + + if ((dir = rb->opendir(testbasedir)) == NULL) + { + if (rb->mkdir(testbasedir) < 0) + { + rb->splashf(HZ*2, "Can't create %s", testbasedir); + testbasedir[0] = '\0'; + return false; + } + } + else + { + rb->closedir(dir); + } + return true; +} + +#ifdef HAVE_MULTIVOLUME +/* Ask which disk to test. */ +static bool select_disk(void) +{ + struct opt_items names[MAX_DISKS]; + int n = disk; + int i; + + for (i = 0; i < ndisks; i++) + { + names[i].string = disks[i]; + names[i].voice_id = -1; + } + rb->set_option("Disk to test", &n, RB_INT, names, ndisks, NULL); + return use_disk(n); +} +#endif + static bool test_fs(void) { unsigned char text_buf[32]; @@ -113,6 +208,7 @@ static bool test_fs(void) log_init(); log_text("test_disk WRITE&VERIFY", true); + log_text(disks[disk], true); #if (CONFIG_PLATFORM & PLATFORM_NATIVE) rb->snprintf(text_buf, sizeof(text_buf), "CPU clock: %ld Hz", *rb->cpu_frequency); @@ -122,7 +218,7 @@ static bool test_fs(void) rb->snprintf(text_buf, sizeof text_buf, "Data size: %dKB", (TEST_SIZE>>10)); log_text(text_buf, true); - fd = rb->creat(TEST_FILE, 0666); + fd = rb->creat(testfile, 0666); if (fd < 0) { rb->splashf(HZ, "creat() failed: %d", fd); @@ -152,7 +248,7 @@ static bool test_fs(void) } rb->close(fd); - fd = rb->open(TEST_FILE, O_RDONLY); + fd = rb->open(testfile, O_RDONLY); if (fd < 0) { rb->splashf(0, "open() failed: %d", fd); @@ -192,7 +288,7 @@ static bool test_fs(void) error: log_close(); - rb->remove(TEST_FILE); + rb->remove(testfile); rb->button_clear_queue(); rb->button_get(true); @@ -212,7 +308,7 @@ static bool file_speed(int chunksize, bool align) log_text("--------------------", true); /* File creation write speed */ - fd = rb->creat(TEST_FILE, 0666); + fd = rb->creat(testfile, 0666); if (fd < 0) { rb->splashf(HZ, "creat() failed: %d", fd); @@ -237,7 +333,7 @@ static bool file_speed(int chunksize, bool align) log_text(text_buf, true); /* Existing file write speed */ - fd = rb->open(TEST_FILE, O_WRONLY); + fd = rb->open(testfile, O_WRONLY); if (fd < 0) { rb->splashf(0, "open() failed: %d", fd); @@ -261,7 +357,7 @@ static bool file_speed(int chunksize, bool align) log_text(text_buf, true); /* File read speed */ - fd = rb->open(TEST_FILE, O_RDONLY); + fd = rb->open(testfile, O_RDONLY); if (fd < 0) { rb->splashf(0, "open() failed: %d", fd); @@ -283,11 +379,11 @@ static bool file_speed(int chunksize, bool align) rb->snprintf(text_buf, sizeof text_buf, "Read (%d,%c): %ld KB/s", chunksize, align ? 'A' : 'U', (25 * (filesize>>8) / time) ); log_text(text_buf, true); - rb->remove(TEST_FILE); + rb->remove(testfile); return true; error: - rb->remove(TEST_FILE); + rb->remove(testfile); return false; } @@ -303,6 +399,7 @@ static bool test_speed(void) rb->memset(audiobuf, 'T', audiobuflen); log_init(); log_text("test_disk SPEED TEST", true); + log_text(disks[disk], true); #if (CONFIG_PLATFORM & PLATFORM_NATIVE) rb->snprintf(text_buf, sizeof(text_buf), "CPU clock: %ld Hz", *rb->cpu_frequency); @@ -314,7 +411,7 @@ static bool test_speed(void) time = *rb->current_tick + TEST_TIME*HZ; for (i = 0; TIME_BEFORE(*rb->current_tick, time); i++) { - rb->snprintf(text_buf, sizeof(text_buf), TESTBASEDIR "/%08x.tmp", i); + tmp_name(text_buf, sizeof(text_buf), i); fd = rb->creat(text_buf, 0666); if (fd < 0) { @@ -335,7 +432,7 @@ static bool test_speed(void) { if (i >= last_file) i = 0; - rb->snprintf(text_buf, sizeof(text_buf), TESTBASEDIR "/%08x.tmp", i); + tmp_name(text_buf, sizeof(text_buf), i); fd = rb->open(text_buf, O_RDONLY); if (fd < 0) { @@ -397,7 +494,7 @@ static bool test_speed(void) time = *rb->current_tick; for (i = 0; i < last_file; i++) { - rb->snprintf(text_buf, sizeof(text_buf), TESTBASEDIR "/%08x.tmp", i); + tmp_name(text_buf, sizeof(text_buf), i); rb->remove(text_buf); } rb->snprintf(text_buf, sizeof(text_buf), "Delete: %ld files/s", @@ -420,7 +517,7 @@ static bool test_speed(void) error: for (i = 0; i < last_file; i++) { - rb->snprintf(text_buf, sizeof(text_buf), TESTBASEDIR "/%08x.tmp", i); + tmp_name(text_buf, sizeof(text_buf), i); rb->remove(text_buf); } log_text("DONE", false); @@ -434,26 +531,26 @@ static bool test_speed(void) /* this is the plugin entry point */ enum plugin_status plugin_start(const void* parameter) { +#ifdef HAVE_MULTIVOLUME + MENUITEM_STRINGLIST(menu, "Test Disk", NULL, + "Disk speed", "Write & verify", "Select disk"); +#else MENUITEM_STRINGLIST(menu, "Test Disk", NULL, "Disk speed", "Write & verify"); +#endif int selected=0; bool quit = false; - DIR *dir; (void)parameter; - if ((dir = rb->opendir(testbasedir)) == NULL) - { - if (rb->mkdir(testbasedir) < 0) - { - rb->splash(HZ*2, "Can't create test directory."); - return PLUGIN_ERROR; - } - } - else - { - rb->closedir(dir); - } + find_disks(); +#ifdef HAVE_MULTIVOLUME + if (ndisks > 1 ? !select_disk() : !use_disk(0)) + return PLUGIN_ERROR; +#else + if (!use_disk(0)) + return PLUGIN_ERROR; +#endif audiobuf = rb->plugin_get_audio_buffer(&audiobuflen); #ifdef STORAGE_WANTS_ALIGN @@ -480,6 +577,12 @@ enum plugin_status plugin_start(const void* parameter) case 1: test_fs(); break; +#ifdef HAVE_MULTIVOLUME + case 2: + if (!select_disk()) + quit = true; + break; +#endif default: quit = true; break; @@ -489,7 +592,8 @@ enum plugin_status plugin_start(const void* parameter) /* Turn on backlight timeout (revert to settings) */ backlight_use_settings(); - rb->rmdir(testbasedir); + if (testbasedir[0]) + rb->rmdir(testbasedir); return PLUGIN_OK; } diff --git a/apps/plugins/test_mem_jpeg.c b/apps/plugins/test_mem_jpeg.c index 855bf71677..e92d9d1d72 100644 --- a/apps/plugins/test_mem_jpeg.c +++ b/apps/plugins/test_mem_jpeg.c @@ -66,7 +66,7 @@ enum plugin_status plugin_start(const void* parameter) int fd = rb->open(filename, O_RDONLY); if (fd < 0) return PLUGIN_ERROR; - unsigned long filesize = rb->filesize(fd); + unsigned long filesize = rb->ffilesize(fd); if (filesize > plugin_buf_len) return PLUGIN_ERROR; plugin_buf_len -= filesize; diff --git a/apps/plugins/test_touchscreen.c b/apps/plugins/test_touchscreen.c index af130c4016..b5999b672a 100644 --- a/apps/plugins/test_touchscreen.c +++ b/apps/plugins/test_touchscreen.c @@ -36,7 +36,7 @@ #elif CONFIG_KEYPAD == CREATIVE_ZENXFI2_PAD #define TOUCHSCREEN_QUIT BUTTON_POWER #define TOUCHSCREEN_TOGGLE BUTTON_MENU -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD #define TOUCHSCREEN_QUIT BUTTON_POWER #define TOUCHSCREEN_TOGGLE BUTTON_PLAY #elif (CONFIG_KEYPAD == ANDROID_PAD) @@ -287,6 +287,55 @@ static void tstest_velocity(void) } } +static void tstest_flick(void) +{ + struct gesture_event gevent; + + rb->splashf(HZ, "Flick test"); + rb->touchscreen_set_mode(TOUCHSCREEN_POINT); + + rb->lcd_clear_display(); + rb->lcd_update(); + + while (true) + { + int action = rb->get_custom_action(CONTEXT_PLUGIN, TIMEOUT_BLOCK, + get_context_map); + if (action == ACTION_TOUCHSCREEN) + { + rb->lcd_clear_display(); + rb->action_gesture_get_event(&gevent); + + switch (rb->gesture_flick_get(&gevent)) { + case GESTURE_FLICK_TOP: + rb->splashf(HZ, "top flick"); + break; + case GESTURE_FLICK_BOTTOM: + rb->splashf(HZ, "bottom flick"); + break; + case GESTURE_FLICK_LEFT: + rb->splashf(HZ, "left flick"); + break; + case GESTURE_FLICK_RIGHT: + rb->splashf(HZ, "right flick"); + break; + } + + rb->lcd_clear_display(); + rb->lcd_update(); + } + else if (action == ACTION_STD_CANCEL) + { + break; + } + else if (rb->default_event_handler(action) == SYS_USB_CONNECTED) + { + usb_exit = true; + break; + } + } +} + /* plugin entry point */ enum plugin_status plugin_start(const void* parameter) { @@ -299,6 +348,7 @@ enum plugin_status plugin_start(const void* parameter) "Pointing mode test", "Gesture test", "Velocity test", + "Flick test", "Quit"); while (true) @@ -322,6 +372,10 @@ enum plugin_status plugin_start(const void* parameter) tstest_velocity(); break; + case 4: + tstest_flick(); + break; + default: return PLUGIN_OK; } diff --git a/apps/plugins/text_editor.c b/apps/plugins/text_editor.c index 2fcdb90b9a..6b7dee868f 100644 --- a/apps/plugins/text_editor.c +++ b/apps/plugins/text_editor.c @@ -413,7 +413,7 @@ enum plugin_status plugin_start(const void* parameter) if (c && !rb->strcmp(c, ".colours")) edit_colors_file = true; #endif - if (buffer_size <= (size_t)rb->filesize(fd) + 0x400) + if (buffer_size <= (size_t)rb->ffilesize(fd) + 0x400) { buffer = rb->plugin_get_audio_buffer(&buffer_size); audio_buf = true; diff --git a/apps/plugins/text_viewer/tv_button.h b/apps/plugins/text_viewer/tv_button.h index 03256fe3fd..c42afdc91b 100644 --- a/apps/plugins/text_viewer/tv_button.h +++ b/apps/plugins/text_viewer/tv_button.h @@ -560,7 +560,7 @@ #define TV_LINE_DOWN BUTTON_SCROLL_FWD #define TV_BOOKMARK BUTTON_PLAY -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD #define TV_BOOKMARK BUTTON_PLAY #elif CONFIG_KEYPAD == MA_PAD @@ -587,7 +587,7 @@ #define TV_LINE_DOWN BUTTON_L #define TV_BOOKMARK BUTTON_A -#elif CONFIG_KEYPAD == CTRU_PAD +#elif CONFIG_KEYPAD == N3DS_PAD #define TV_QUIT BUTTON_BACK #define TV_SCROLL_UP BUTTON_UP #define TV_SCROLL_DOWN BUTTON_DOWN @@ -597,6 +597,16 @@ #define TV_AUTOSCROLL BUTTON_USER #define TV_BOOKMARK BUTTON_SELECT +#elif CONFIG_KEYPAD == ECHO_R1_PAD +#define TV_QUIT BUTTON_POWER +#define TV_SCROLL_UP BUTTON_UP +#define TV_SCROLL_DOWN BUTTON_DOWN +#define TV_SCREEN_LEFT BUTTON_LEFT +#define TV_SCREEN_RIGHT BUTTON_RIGHT +#define TV_MENU BUTTON_START +#define TV_AUTOSCROLL BUTTON_A +#define TV_BOOKMARK BUTTON_B + #else #error No keymap defined! #endif diff --git a/apps/plugins/text_viewer/tv_reader.c b/apps/plugins/text_viewer/tv_reader.c index d4f9040609..70448582d4 100644 --- a/apps/plugins/text_viewer/tv_reader.c +++ b/apps/plugins/text_viewer/tv_reader.c @@ -144,7 +144,7 @@ static int tv_change_preferences(const struct tv_preferences *oldp) if (fd < 0) return TV_CALLBACK_ERROR; - file_size = rb->filesize(fd); + file_size = rb->ffilesize(fd); change_file = true; } diff --git a/apps/plugins/viewers.config b/apps/plugins/viewers.config index d745a29cfe..151b97ad7c 100644 --- a/apps/plugins/viewers.config +++ b/apps/plugins/viewers.config @@ -76,6 +76,11 @@ wav,viewers/wavview,10 wav,viewers/wav2wv,- wav,viewers/mp3_encoder,- wav,viewers/test_codec,- +#ifdef HAVE_HW_H264 +mp4,viewers/h264_player,4 +m4v,viewers/h264_player,4 +mov,viewers/h264_player,4 +#endif mpg,viewers/mpegplayer,4 mpeg,viewers/mpegplayer,4 mpv,viewers/mpegplayer,4 diff --git a/apps/plugins/vu_meter.c b/apps/plugins/vu_meter.c index d18b6a658c..1789b9bb89 100644 --- a/apps/plugins/vu_meter.c +++ b/apps/plugins/vu_meter.c @@ -443,7 +443,7 @@ #define LABEL_MENU "MENU" #define LABEL_VOLUME "VOL+/VOL-" -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD /* use touchscreen */ #elif CONFIG_KEYPAD == MA_PAD @@ -468,7 +468,7 @@ #define LABEL_MENU "B" #define LABEL_VOLUME "UP/DOWN" -#elif CONFIG_KEYPAD == CTRU_PAD +#elif CONFIG_KEYPAD == N3DS_PAD #define VUMETER_QUIT BUTTON_BACK #define VUMETER_HELP BUTTON_USER #define VUMETER_MENU BUTTON_MENU @@ -479,6 +479,17 @@ #define LABEL_MENU "X" #define LABEL_VOLUME "Up/Down" +#elif CONFIG_KEYPAD == ECHO_R1_PAD +#define VUMETER_QUIT BUTTON_POWER +#define VUMETER_HELP BUTTON_SELECT +#define VUMETER_MENU BUTTON_START +#define VUMETER_UP BUTTON_UP +#define VUMETER_DOWN BUTTON_DOWN +#define LABEL_HELP "Select" +#define LABEL_QUIT "Power" +#define LABEL_MENU "Start" +#define LABEL_VOLUME "Up/Down" + #else #error No keymap defined! #endif diff --git a/apps/plugins/wormlet.c b/apps/plugins/wormlet.c index 5c30c7eb72..e5ccc26958 100644 --- a/apps/plugins/wormlet.c +++ b/apps/plugins/wormlet.c @@ -396,7 +396,7 @@ CONFIG_KEYPAD == MROBE500_PAD #define BTN_QUIT BUTTON_POWER #define BTN_STOPRESET BUTTON_BACK -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD /* use touchscreen */ #elif CONFIG_KEYPAD == MA_PAD @@ -418,7 +418,7 @@ CONFIG_KEYPAD == MROBE500_PAD #define BTN_QUIT BUTTON_START #define BTN_STOPRESET BUTTON_B -#elif CONFIG_KEYPAD == CTRU_PAD +#elif CONFIG_KEYPAD == N3DS_PAD #define BTN_DIR_UP BUTTON_UP #define BTN_DIR_DOWN BUTTON_DOWN @@ -428,6 +428,16 @@ CONFIG_KEYPAD == MROBE500_PAD #define BTN_QUIT BUTTON_BACK #define BTN_STOPRESET BUTTON_MENU +#elif CONFIG_KEYPAD == ECHO_R1_PAD + +#define BTN_DIR_UP BUTTON_UP +#define BTN_DIR_DOWN BUTTON_DOWN +#define BTN_DIR_LEFT BUTTON_LEFT +#define BTN_DIR_RIGHT BUTTON_RIGHT +#define BTN_STARTPAUSE BUTTON_START +#define BTN_QUIT BUTTON_POWER +#define BTN_STOPRESET BUTTON_SELECT + #else #error No keymap defined! #endif @@ -513,9 +523,9 @@ CONFIG_KEYPAD == MROBE500_PAD #define SPEED 4 #define MAX_WORM_SEGMENTS 512 #elif ((LCD_WIDTH == 240) && (LCD_HEIGHT == 240)) || \ - ((LCD_WIDTH == 320) && (LCD_HEIGHT == 240)) || \ + (((LCD_WIDTH == 320) || (LCD_WIDTH == 400)) && (LCD_HEIGHT == 240)) || \ ((LCD_WIDTH == 240) && ((LCD_HEIGHT == 320) || (LCD_HEIGHT == 400))) || \ - ((LCD_WIDTH == 360) && (LCD_HEIGHT == 400)) + ((LCD_WIDTH == 360) && ((LCD_HEIGHT == 400) || (LCD_HEIGHT == 640))) #define FOOD_SIZE 7 #define ARGH_SIZE 8 #define SPEED 4 diff --git a/apps/plugins/xobox.c b/apps/plugins/xobox.c index 84f8fe8698..d29ac579b2 100644 --- a/apps/plugins/xobox.c +++ b/apps/plugins/xobox.c @@ -350,7 +350,7 @@ CONFIG_KEYPAD == MROBE500_PAD #define DOWN BUTTON_DOWN #define PAUSE BUTTON_PLAY -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD /* use touchscreen */ #elif CONFIG_KEYPAD == RG_NANO_PAD @@ -362,7 +362,7 @@ CONFIG_KEYPAD == MROBE500_PAD #define DOWN BUTTON_DOWN #define PAUSE BUTTON_A -#elif CONFIG_KEYPAD == CTRU_PAD +#elif CONFIG_KEYPAD == N3DS_PAD #define QUIT BUTTON_BACK #define LEFT BUTTON_LEFT @@ -371,6 +371,15 @@ CONFIG_KEYPAD == MROBE500_PAD #define DOWN BUTTON_DOWN #define PAUSE BUTTON_SELECT +#elif CONFIG_KEYPAD == ECHO_R1_PAD + +#define QUIT BUTTON_POWER +#define LEFT BUTTON_LEFT +#define RIGHT BUTTON_RIGHT +#define UP BUTTON_UP +#define DOWN BUTTON_DOWN +#define PAUSE BUTTON_START + #else #error "No keymap defined!" #endif diff --git a/apps/plugins/zxbox/keymaps.h b/apps/plugins/zxbox/keymaps.h index 0bb7d3e80f..d2ffb33fe6 100644 --- a/apps/plugins/zxbox/keymaps.h +++ b/apps/plugins/zxbox/keymaps.h @@ -282,7 +282,7 @@ #define ZX_UP BUTTON_UP #define ZX_DOWN BUTTON_DOWN -#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD +#elif CONFIG_KEYPAD == SHANLING_Q1_PAD || CONFIG_KEYPAD == HIBY_R3PROII_PAD || CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD /* use touchscreen */ #elif CONFIG_KEYPAD == MA_PAD @@ -301,7 +301,7 @@ #define ZX_SELECT BUTTON_A #define ZX_MENU BUTTON_START -#elif CONFIG_KEYPAD == CTRU_PAD +#elif CONFIG_KEYPAD == N3DS_PAD #define ZX_UP BUTTON_UP #define ZX_DOWN BUTTON_DOWN @@ -310,6 +310,15 @@ #define ZX_SELECT BUTTON_SELECT #define ZX_MENU BUTTON_MENU +#elif CONFIG_KEYPAD == ECHO_R1_PAD + +#define ZX_UP BUTTON_UP +#define ZX_DOWN BUTTON_DOWN +#define ZX_LEFT BUTTON_LEFT +#define ZX_RIGHT BUTTON_RIGHT +#define ZX_SELECT BUTTON_A +#define ZX_MENU BUTTON_POWER + #else #error Keymap not defined! diff --git a/apps/plugins/zxbox/zxbox_keyb.c b/apps/plugins/zxbox/zxbox_keyb.c index 6a5c2a6c81..55629d4f60 100644 --- a/apps/plugins/zxbox/zxbox_keyb.c +++ b/apps/plugins/zxbox/zxbox_keyb.c @@ -291,7 +291,7 @@ #define KBD_UP BUTTON_UP #define KBD_DOWN BUTTON_DOWN -#elif CONFIG_KEYPAD == CTRU_PAD +#elif CONFIG_KEYPAD == N3DS_PAD #define KBD_SELECT BUTTON_SELECT #define KBD_ABORT BUTTON_BACK @@ -300,6 +300,15 @@ #define KBD_UP BUTTON_UP #define KBD_DOWN BUTTON_DOWN +#elif CONFIG_KEYPAD == ECHO_R1_PAD + +#define KBD_SELECT BUTTON_A +#define KBD_ABORT BUTTON_B +#define KBD_LEFT BUTTON_LEFT +#define KBD_RIGHT BUTTON_RIGHT +#define KBD_UP BUTTON_UP +#define KBD_DOWN BUTTON_DOWN + #endif #ifdef HAVE_TOUCHSCREEN diff --git a/apps/radio/radio.c b/apps/radio/radio.c index a9b53971b2..417164fec2 100644 --- a/apps/radio/radio.c +++ b/apps/radio/radio.c @@ -97,6 +97,12 @@ #define FM_PLAY #define FM_MODE +#elif (CONFIG_KEYPAD == RK27XX_GENERIC_PAD) +#define FM_MENU +#define FM_PLAY +#define FM_STOP +#define FM_EXIT + #elif (CONFIG_KEYPAD == SAMSUNG_YPR0_PAD) #define FM_MENU #define FM_PRESET @@ -106,6 +112,7 @@ #define FM_PLAY #define FM_PREV_PRESET #define FM_NEXT_PRESET +#define FM_RECORD #elif (CONFIG_KEYPAD == SANSA_FUZEPLUS_PAD) #define FM_PRESET_ADD @@ -395,8 +402,16 @@ void radio_screen(void) (radio_status == FMRADIO_PAUSED) ? SRCF_FMRADIO_PAUSED : SRCF_FMRADIO_PLAYING); - if(radio_preset_count() < 1 && yesno_pop(ID2P(LANG_FM_FIRST_AUTOSCAN))) - presets_scan(NULL); + /* The question and the scan clear the screen: set it up again after, + * as for the other screens shown from here - the skin redraws only + * its viewports, and its backdrop would stay missing elsewhere. */ + if(radio_preset_count() < 1) + { + fms_fix_displays(FMS_EXIT); + if(yesno_pop(ID2P(LANG_FM_FIRST_AUTOSCAN))) + presets_scan(NULL); + fms_fix_displays(FMS_ENTER); + } preset_set_current(preset_find(curr_freq)); if(radio_current_preset() != -1) diff --git a/apps/recorder/jpeg_common.h b/apps/recorder/jpeg_common.h index fb152feb05..eaa439924c 100644 --- a/apps/recorder/jpeg_common.h +++ b/apps/recorder/jpeg_common.h @@ -27,6 +27,7 @@ #ifndef _JPEG_COMMON_H #define _JPEG_COMMON_H +#include #include "bmp.h" #define HUFF_LOOKAHEAD 8 /* # of bits of lookahead */ @@ -80,6 +81,40 @@ union uint8_rgbyuv { struct uint8_rgb rgb; }; +/* Record what an APP0 (JFIF) or APP14 (Adobe) segment says about the + * colour space, given its first n bytes (at most 12 are needed). *adobe gets + * the Adobe transform flag + 1. */ +static inline void jpeg_app_colorspace(int marker, const unsigned char *id, + int n, bool *jfif, unsigned char *adobe) +{ + bool is_jfif = marker == 0xE0; + const char *sig = is_jfif ? "JFIF" : "Adobe"; /* 5 bytes with the NUL */ + int i; + + if (n < (is_jfif ? 5 : 12)) + return; + for (i = 0; i < 5; i++) + if (id[i] != (unsigned char)sig[i]) + return; + if (is_jfif) + *jfif = true; + else + *adobe = id[11] + 1; +} + +/* Whether a 3-component image holds RGB rather than YCbCr, decided as + * libjpeg does: a JFIF marker means YCbCr; otherwise an Adobe marker's + * transform flag decides (0 is RGB); otherwise component IDs 'R', 'G', 'B' + * mean RGB. adobe is the transform flag + 1, or 0 without an Adobe marker. */ +static inline bool jpeg_is_rgb(bool jfif, int adobe, int id0, int id1, int id2) +{ + if (jfif) + return false; + if (adobe) + return adobe == 1; + return id0 == 'R' && id1 == 'G' && id2 == 'B'; +} + static inline int clamp_component(int x) { if(x > 255) return 255; diff --git a/apps/recorder/jpeg_load.c b/apps/recorder/jpeg_load.c index 3214cc9763..334b407774 100644 --- a/apps/recorder/jpeg_load.c +++ b/apps/recorder/jpeg_load.c @@ -56,6 +56,13 @@ typedef uint8_t jpeg_pix_t; #endif #define JPEG_IDCT_TRANSPOSE #define JPEG_PIX_SZ (sizeof(jpeg_pix_t)) +/* index into the per-scale arrays: luma or chroma in colour builds; a + greyscale build only decodes chroma for RGB, which is at the luma scale */ +#ifdef HAVE_LCD_COLOR +#define SCALE_IDX(ci) (!!(ci)) +#else +#define SCALE_IDX(ci) 0 +#endif #ifdef HAVE_LCD_COLOR #define COLOR_EXTRA_IDCT_WS 64 #else @@ -106,14 +113,16 @@ struct jpeg int cur_row; /* current row relative to top of image */ int set_rows; int store_pos[4]; /* for Y block ordering */ -#ifdef HAVE_LCD_COLOR + bool jfif; /* saw a JFIF APP0 marker */ + unsigned char adobe; /* Adobe APP14 transform flag + 1, 0 if none */ + bool rgb; /* the components are R, G, B rather than Y, Cb, Cr */ int last_dc_val[3]; +#ifdef HAVE_LCD_COLOR int h_scale[2]; /* horizontal scalefactor = (2**N) / 8 */ int v_scale[2]; /* same as above, for vertical direction */ int k_need[2]; /* per component zig-zag index of last needed coefficient */ int zero_need[2]; /* per compenent number of coefficients to zero */ #else - int last_dc_val; int h_scale[1]; /* horizontal scalefactor = (2**N) / 8 */ int v_scale[1]; /* same as above, for vertical direction */ int k_need[1]; /* per component zig-zag index of last needed coefficient */ @@ -128,12 +137,10 @@ struct jpeg struct derived_tbl ac_derived_tbls[2]; struct frame_component frameheader[3]; /* Component descriptor */ - struct scan_component scanheader[3]; /* currently not used */ + struct scan_component scanheader[3]; /* Huffman tables per component */ - int mcu_membership[6]; /* info per block */ - int tab_membership[6]; - int subsample_x[3]; /* info per component */ - int subsample_y[3]; + int mcu_membership[6]; /* component of each block */ + unsigned char mcu_pos[6]; /* position of each block in its component */ bool resize; unsigned char buf[JPEG_READ_BUF_SIZE]; struct img_part part; @@ -1012,6 +1019,7 @@ static int process_markers(struct jpeg* p_jpeg) break; /* discard */ case 0xC0: /* SOF Huff - Baseline DCT */ + case 0xC1: /* SOF Huff - Extended sequential DCT (8 bit samples) */ { JDEBUGF("SOF marker "); ret |= SOF0; @@ -1044,15 +1052,37 @@ static int process_markers(struct jpeg* p_jpeg) p_jpeg->frameheader[i].vertical_sampling = c & 0x0F; p_jpeg->frameheader[i].quanttable_select = e_getc(p_jpeg, -1); + if (p_jpeg->frameheader[i].quanttable_select > 3) + return -8; /* Unsupported quantization table */ if (p_jpeg->frameheader[i].horizontal_sampling > 2 || p_jpeg->frameheader[i].vertical_sampling > 2) return -3; /* Unsupported SOF0 subsampling */ } p_jpeg->blocks = n; + for (i=1; iframeheader[i].horizontal_sampling; + int v = p_jpeg->frameheader[i].vertical_sampling; + if ((h != 1 || v != 1) + && (h != p_jpeg->frameheader[0].horizontal_sampling + || v != p_jpeg->frameheader[0].vertical_sampling + || h == v)) + return -3; /* Unsupported SOF0 subsampling */ + } + if (p_jpeg->x_size == 0 || p_jpeg->y_size == 0) + return -12; /* Height defined by DNL not supported */ + /* A single-component scan is non-interleaved: the MCU is one + 8x8 block regardless of the sampling factors (T.81 A.2.2) */ + if (n == 1) + { + p_jpeg->frameheader[0].horizontal_sampling = 1; + p_jpeg->frameheader[0].vertical_sampling = 1; + } } break; - case 0xC1: /* SOF Huff - Extended sequential DCT*/ case 0xC2: /* SOF Huff - Progressive DCT*/ case 0xC3: /* SOF Huff - Spatial (sequential) lossless*/ case 0xC5: /* SOF Huff - Differential sequential DCT*/ @@ -1152,7 +1182,10 @@ static int process_markers(struct jpeg* p_jpeg) marker_size -= 2; n = (marker_size-1-3)/2; - if (e_getc(p_jpeg, -1) != n || (n != 1 && n != 3)) + if (e_getc(p_jpeg, -1) != n || (n != 1 && n != 3) + /* one scan with all components; multi-scan files and + SOS before SOF are not supported (blocks = Nf here) */ + || n != p_jpeg->blocks) { return (-7); /* Unsupported SOS component specification */ } @@ -1160,11 +1193,31 @@ static int process_markers(struct jpeg* p_jpeg) for (i=0; iscanheader[i].ID = e_getc(p_jpeg, -1); + if (p_jpeg->scanheader[i].ID != p_jpeg->frameheader[i].ID) + { + return (-7); /* components out of frame order */ + } p_jpeg->scanheader[i].DC_select = (c = e_getc(p_jpeg, -1)) >> 4; p_jpeg->scanheader[i].AC_select = c & 0x0F; marker_size -= 2; + if (p_jpeg->scanheader[i].DC_select > 1 + || p_jpeg->scanheader[i].AC_select > 1) + { + return (-5); /* Huffman table index out of range */ + } } + p_jpeg->rgb = n == 3 && jpeg_is_rgb(p_jpeg->jfif, p_jpeg->adobe, + p_jpeg->frameheader[0].ID, p_jpeg->frameheader[1].ID, + p_jpeg->frameheader[2].ID); +#ifndef HAVE_LCD_COLOR + /* greyscale RGB is combined per block, so all components + must be one block per MCU (chroma is then 1x1 too) */ + if (p_jpeg->rgb + && (p_jpeg->frameheader[0].horizontal_sampling != 1 + || p_jpeg->frameheader[0].vertical_sampling != 1)) + return -3; /* Unsupported SOF0 subsampling */ +#endif /* skip spectral information */ e_skip_bytes(p_jpeg, marker_size); done = true; @@ -1178,20 +1231,25 @@ static int process_markers(struct jpeg* p_jpeg) marker_size |= e_getc(p_jpeg, -1); /* Lowbyte */ marker_size -= 2; - n = (marker_size)/(QUANT_TABLE_LENGTH+1); /* # of tables */ - for (i=0; i QUANT_TABLE_LENGTH) /* another table */ { - int id = e_getc(p_jpeg, -1); /* ID */ - marker_size--; - if (id >= 4) + int id = e_getc(p_jpeg, -1); /* Pq: precision, Tq: ID */ + int pq = id >> 4; + id &= 0x0F; + marker_size -= 1 + QUANT_TABLE_LENGTH * (pq + 1); + if (id >= 4 || pq > 1 || marker_size < 0) { return (-8); /* Unsupported quantization table */ } /* Read Quantisation table: */ for (j=0; jquanttable[id][j] = e_getc(p_jpeg, -1); - marker_size--; + int q = e_getc(p_jpeg, -1); + if (pq) /* 16 bit entries (SOF1) */ + q = q << 8 | e_getc(p_jpeg, -1); + if (q > 8191) + return (-8); /* too big once scaled for the IDCT */ + p_jpeg->quanttable[id][j] = q; } } e_skip_bytes(p_jpeg, marker_size); @@ -1231,11 +1289,22 @@ static int process_markers(struct jpeg* p_jpeg) case 0xEF: /* Application Field 15*/ case 0xFE: /* Comment */ { + unsigned char id[12]; + int marker = c; marker_size = e_getc(p_jpeg, -1) << 8; /* Highbyte */ marker_size |= e_getc(p_jpeg, -1); /* Lowbyte */ marker_size -= 2; + n = 0; + if (marker == 0xE0 || marker == 0xEE) + { /* JFIF or Adobe: may say which colour space is used */ + n = MIN(marker_size, (int)sizeof(id)); + for (i = 0; i < n; i++) + id[i] = e_getc(p_jpeg, -1); + jpeg_app_colorspace(marker, id, n, &p_jpeg->jfif, + &p_jpeg->adobe); + } JDEBUGF("unhandled marker len %d\n", marker_size); - e_skip_bytes(p_jpeg, marker_size); /* skip segment */ + e_skip_bytes(p_jpeg, marker_size - n); /* skip segment */ } break; @@ -1447,113 +1516,39 @@ static const unsigned char zig[] = /* Reformat some image header data so that the decoder can use it properly. */ INLINE void fix_headers(struct jpeg* p_jpeg) { - int i; + int h = p_jpeg->frameheader[0].horizontal_sampling; + int v = p_jpeg->frameheader[0].vertical_sampling; + int ncomp = p_jpeg->blocks; /* still the component count here */ + int i, c, b; for (i=0; i<4; i++) p_jpeg->store_pos[i] = i; /* default ordering */ - - /* assignments for the decoding of blocks */ - if (p_jpeg->frameheader[0].horizontal_sampling == 2 - && p_jpeg->frameheader[0].vertical_sampling == 1) - { /* 4:2:2 */ - p_jpeg->blocks = 4; - p_jpeg->x_mbl = (p_jpeg->x_size+15) / 16; - p_jpeg->x_phys = p_jpeg->x_mbl * 16; - p_jpeg->y_mbl = (p_jpeg->y_size+7) / 8; - p_jpeg->y_phys = p_jpeg->y_mbl * 8; - p_jpeg->mcu_membership[0] = 0; /* Y1=Y2=0, U=1, V=2 */ - p_jpeg->mcu_membership[1] = 0; - p_jpeg->mcu_membership[2] = 1; - p_jpeg->mcu_membership[3] = 2; - p_jpeg->tab_membership[0] = 0; /* DC, DC, AC, AC */ - p_jpeg->tab_membership[1] = 0; - p_jpeg->tab_membership[2] = 1; - p_jpeg->tab_membership[3] = 1; - p_jpeg->subsample_x[0] = 1; - p_jpeg->subsample_x[1] = 2; - p_jpeg->subsample_x[2] = 2; - p_jpeg->subsample_y[0] = 1; - p_jpeg->subsample_y[1] = 1; - p_jpeg->subsample_y[2] = 1; - } - if (p_jpeg->frameheader[0].horizontal_sampling == 1 - && p_jpeg->frameheader[0].vertical_sampling == 2) - { /* 4:2:2 vertically subsampled */ - p_jpeg->store_pos[1] = 2; /* block positions are mirrored */ + if (h == 1 && v == 2) + { /* the second block of a component goes below the first */ + p_jpeg->store_pos[1] = 2; p_jpeg->store_pos[2] = 1; - p_jpeg->blocks = 4; - p_jpeg->x_mbl = (p_jpeg->x_size+7) / 8; - p_jpeg->x_phys = p_jpeg->x_mbl * 8; - p_jpeg->y_mbl = (p_jpeg->y_size+15) / 16; - p_jpeg->y_phys = p_jpeg->y_mbl * 16; - p_jpeg->mcu_membership[0] = 0; /* Y1=Y2=0, U=1, V=2 */ - p_jpeg->mcu_membership[1] = 0; - p_jpeg->mcu_membership[2] = 1; - p_jpeg->mcu_membership[3] = 2; - p_jpeg->tab_membership[0] = 0; /* DC, DC, AC, AC */ - p_jpeg->tab_membership[1] = 0; - p_jpeg->tab_membership[2] = 1; - p_jpeg->tab_membership[3] = 1; - p_jpeg->subsample_x[0] = 1; - p_jpeg->subsample_x[1] = 1; - p_jpeg->subsample_x[2] = 1; - p_jpeg->subsample_y[0] = 1; - p_jpeg->subsample_y[1] = 2; - p_jpeg->subsample_y[2] = 2; - } - else if (p_jpeg->frameheader[0].horizontal_sampling == 2 - && p_jpeg->frameheader[0].vertical_sampling == 2) - { /* 4:2:0 */ - p_jpeg->blocks = 6; - p_jpeg->x_mbl = (p_jpeg->x_size+15) / 16; - p_jpeg->x_phys = p_jpeg->x_mbl * 16; - p_jpeg->y_mbl = (p_jpeg->y_size+15) / 16; - p_jpeg->y_phys = p_jpeg->y_mbl * 16; - p_jpeg->mcu_membership[0] = 0; - p_jpeg->mcu_membership[1] = 0; - p_jpeg->mcu_membership[2] = 0; - p_jpeg->mcu_membership[3] = 0; - p_jpeg->mcu_membership[4] = 1; - p_jpeg->mcu_membership[5] = 2; - p_jpeg->tab_membership[0] = 0; - p_jpeg->tab_membership[1] = 0; - p_jpeg->tab_membership[2] = 0; - p_jpeg->tab_membership[3] = 0; - p_jpeg->tab_membership[4] = 1; - p_jpeg->tab_membership[5] = 1; - p_jpeg->subsample_x[0] = 1; - p_jpeg->subsample_x[1] = 2; - p_jpeg->subsample_x[2] = 2; - p_jpeg->subsample_y[0] = 1; - p_jpeg->subsample_y[1] = 2; - p_jpeg->subsample_y[2] = 2; - } - else if (p_jpeg->frameheader[0].horizontal_sampling == 1 - && p_jpeg->frameheader[0].vertical_sampling == 1) - { /* 4:4:4 */ - /* don't overwrite p_jpeg->blocks */ - p_jpeg->x_mbl = (p_jpeg->x_size+7) / 8; - p_jpeg->x_phys = p_jpeg->x_mbl * 8; - p_jpeg->y_mbl = (p_jpeg->y_size+7) / 8; - p_jpeg->y_phys = p_jpeg->y_mbl * 8; - p_jpeg->mcu_membership[0] = 0; - p_jpeg->mcu_membership[1] = 1; - p_jpeg->mcu_membership[2] = 2; - p_jpeg->tab_membership[0] = 0; - p_jpeg->tab_membership[1] = 1; - p_jpeg->tab_membership[2] = 1; - p_jpeg->subsample_x[0] = 1; - p_jpeg->subsample_x[1] = 1; - p_jpeg->subsample_x[2] = 1; - p_jpeg->subsample_y[0] = 1; - p_jpeg->subsample_y[1] = 1; - p_jpeg->subsample_y[2] = 1; - } - else - { - /* error */ } + p_jpeg->x_mbl = (p_jpeg->x_size + 8*h - 1) / (8*h); + p_jpeg->x_phys = p_jpeg->x_mbl * 8*h; + p_jpeg->y_mbl = (p_jpeg->y_size + 8*v - 1) / (8*v); + p_jpeg->y_phys = p_jpeg->y_mbl * 8*v; + + /* Blocks in MCU order: each component's blocks in turn, H x V of them. + Chroma is either 1x1 or sampled like luma (checked in SOF), so an MCU + holds at most 6 blocks. */ + b = 0; + for (c = 0; c < ncomp; c++) + { + int n = p_jpeg->frameheader[c].horizontal_sampling + * p_jpeg->frameheader[c].vertical_sampling; + for (i = 0; i < n; i++, b++) + { + p_jpeg->mcu_membership[b] = c; + p_jpeg->mcu_pos[b] = i; + } + } + p_jpeg->blocks = b; } INLINE void fix_huff_tables(struct jpeg *p_jpeg) @@ -1573,23 +1568,61 @@ INLINE void fix_huff_tables(struct jpeg *p_jpeg) * quantization table when one of these IDCT routines is used, rather than * have the IDCT shift each value it processes. */ +/* Pre-scale the quantization tables for the IDCT. Luma and chroma can use + * different IDCT scales, so a table that both select is copied to a slot no + * component selects before scaling; each component's quanttable_select is + * rewritten to the slot it should use. There are 4 slots and at most 3 + * components, so when a table is shared a free slot always exists. */ INLINE void fix_quant_tables(struct jpeg *p_jpeg) { - int shift, i, j; + int want[4] = { -1, -1, -1, -1 }; /* scale shift wanted for each slot */ + int orig[4] = { 0, 1, 2, 3 }; /* table each slot holds */ + int c, c2, f, j; #ifdef HAVE_LCD_COLOR - const int k = 2; + const int nc = p_jpeg->blocks > 1 ? 3 : 1; #else - const int k = 1; + /* chroma is not decoded; RGB is, at the luma scale */ + const int nc = p_jpeg->rgb ? 3 : 1; #endif - for (i = 0; i < k; i++) + for (c = 0; c < nc; c++) { - shift = idct_tbl[p_jpeg->v_scale[i]].scale; - if (shift) + int t = p_jpeg->frameheader[c].quanttable_select; + int shift = idct_tbl[p_jpeg->v_scale[SCALE_IDX(c)]].scale; + + for (f = 0; f < 4; f++) /* a slot already holding t at this scale? */ + if (orig[f] == t && want[f] == shift) + break; + if (f == 4 && want[t] < 0) + f = t; /* first use of t */ + if (f == 4) + { /* t is in use at another scale: copy it to a free slot that no + later component still needs */ + for (f = 0; f < 4; f++) + { + if (want[f] >= 0) + continue; + for (c2 = c + 1; c2 < nc; c2++) + if (p_jpeg->frameheader[c2].quanttable_select == f) + break; + if (c2 == nc) + break; + } + MEMCPY(p_jpeg->quanttable[f], p_jpeg->quanttable[t], + sizeof(p_jpeg->quanttable[f])); + orig[f] = t; + } + want[f] = shift; + p_jpeg->frameheader[c].quanttable_select = f; + } + + for (f = 0; f < 4; f++) + { + if (want[f] > 0) { for (j = 0; j < 64; j++) - p_jpeg->quanttable[i][j] <<= shift; + p_jpeg->quanttable[f][j] <<= want[f]; } } } @@ -1804,15 +1837,10 @@ static void search_restart(struct jpeg *p_jpeg) static struct img_part *store_row_jpeg(void *jpeg_args) { struct jpeg *p_jpeg = (struct jpeg*) jpeg_args; -#ifdef HAVE_LCD_COLOR - int mcu_hscale = p_jpeg->h_scale[1]; - int mcu_vscale = p_jpeg->v_scale[1]; -#else int mcu_hscale = (p_jpeg->h_scale[0] + p_jpeg->frameheader[0].horizontal_sampling - 1); int mcu_vscale = (p_jpeg->v_scale[0] + p_jpeg->frameheader[0].vertical_sampling - 1); -#endif unsigned int width = p_jpeg->x_mbl << mcu_hscale; unsigned int b_width = width * JPEG_PIX_SZ; int height = BIT_N(mcu_vscale); @@ -1832,42 +1860,41 @@ static struct img_part *store_row_jpeg(void *jpeg_args) store_offs[p_jpeg->store_pos[3]] = store_offs[1] + store_offs[2]; /* decoded DCT coefficients */ int16_t block[IDCT_WS_SIZE] __attribute__((aligned(8))); +#ifndef HAVE_LCD_COLOR + unsigned char rgb_tmp[2][64]; /* R and G of a greyscale RGB MCU */ +#endif for (x = 0; x < p_jpeg->x_mbl; x++) { int blkn; for (blkn = 0; blkn < p_jpeg->blocks; blkn++) { int ci = p_jpeg->mcu_membership[blkn]; /* component index */ - int ti = p_jpeg->tab_membership[blkn]; /* table index */ #ifdef JPEG_IDCT_TRANSPOSE - bool transpose = p_jpeg->v_scale[!!ci] > 2; + bool transpose = p_jpeg->v_scale[SCALE_IDX(ci)] > 2; #endif int k = 1; /* coefficient index */ int s, r; /* huffman values */ - struct derived_tbl* dctbl = &p_jpeg->dc_derived_tbls[ti]; - struct derived_tbl* actbl = &p_jpeg->ac_derived_tbls[ti]; + struct derived_tbl* dctbl = + &p_jpeg->dc_derived_tbls[p_jpeg->scanheader[ci].DC_select]; + struct derived_tbl* actbl = + &p_jpeg->ac_derived_tbls[p_jpeg->scanheader[ci].AC_select]; + const int16_t *qt = p_jpeg->quanttable[ + p_jpeg->frameheader[ci].quanttable_select]; /* Section F.2.2.1: decode the DC coefficient difference */ huff_decode_dc(p_jpeg, dctbl, s, r); #ifndef HAVE_LCD_COLOR - if (!ci) + if (!ci || p_jpeg->rgb) #endif { s = HUFF_EXTEND(r, s); -#ifdef HAVE_LCD_COLOR p_jpeg->last_dc_val[ci] += s; /* output it (assumes zag[0] = 0) */ - block[0] = MULTIPLY16(p_jpeg->last_dc_val[ci], - p_jpeg->quanttable[!!ci][0]); -#else - p_jpeg->last_dc_val += s; - /* output it (assumes zag[0] = 0) */ - block[0] = MULTIPLY16(p_jpeg->last_dc_val, - p_jpeg->quanttable[0][0]); -#endif + block[0] = MULTIPLY16(p_jpeg->last_dc_val[ci], qt[0]); /* coefficient buffer must be cleared */ - MEMSET(block+1, 0, p_jpeg->zero_need[!!ci] * sizeof(int)); + MEMSET(block+1, 0, + p_jpeg->zero_need[SCALE_IDX(ci)] * sizeof(int)); /* Section F.2.2.2: decode the AC coefficients */ while(true) { @@ -1878,11 +1905,11 @@ static struct img_part *store_row_jpeg(void *jpeg_args) if (s) { check_bit_buffer(p_jpeg, s); - if (k >= p_jpeg->k_need[!!ci]) + if (k >= p_jpeg->k_need[SCALE_IDX(ci)]) goto skip_rest; r = get_bits(p_jpeg, s); r = HUFF_EXTEND(r, s); - r = MULTIPLY16(r, p_jpeg->quanttable[!!ci][k]); + r = MULTIPLY16(r, qt[k]); #ifdef JPEG_IDCT_TRANSPOSE block[zag[transpose ? k : k + 64]] = r ; #else @@ -1920,25 +1947,52 @@ skip_rest: } /* for k */ block_end: #ifndef HAVE_LCD_COLOR - if (!ci) + if (!ci || p_jpeg->rgb) #endif { - int idct_cols = BIT_N(MIN(p_jpeg->h_scale[!!ci], 3)); - int idct_rows = BIT_N(p_jpeg->v_scale[!!ci]); - unsigned char *b_out = out + (ci ? ci : store_offs[blkn]); - if (idct_tbl[p_jpeg->v_scale[!!ci]].v_idct) + int sc = SCALE_IDX(ci); + int idct_cols = BIT_N(MIN(p_jpeg->h_scale[sc], 3)); + int idct_rows = BIT_N(p_jpeg->v_scale[sc]); + int rowstep = b_width; +#ifdef HAVE_LCD_COLOR + /* Y, Cb, Cr go to bytes 0-2 for the YUV conversion; + R, G, B to their places in struct uint8_rgb */ + unsigned char *b_out = out + (p_jpeg->rgb ? 2 - ci : ci) + + store_offs[p_jpeg->mcu_pos[blkn]]; +#else + unsigned char *b_out = out + + store_offs[p_jpeg->mcu_pos[blkn]]; + if (ci < 2 && p_jpeg->rgb) + { /* keep R and G until B is decoded */ + b_out = rgb_tmp[ci]; + rowstep = 8; + } +#endif + if (idct_tbl[p_jpeg->v_scale[sc]].v_idct) #ifdef JPEG_IDCT_TRANSPOSE - idct_tbl[p_jpeg->v_scale[!!ci]].v_idct(block, + idct_tbl[p_jpeg->v_scale[sc]].v_idct(block, transpose ? block + 8 * idct_cols : block + idct_cols); uint16_t * h_block = transpose ? block + 64 : block; - idct_tbl[p_jpeg->h_scale[!!ci]].h_idct(h_block, b_out, - h_block + idct_rows * 8, b_width); + idct_tbl[p_jpeg->h_scale[sc]].h_idct(h_block, b_out, + h_block + idct_rows * 8, rowstep); #else - idct_tbl[p_jpeg->v_scale[!!ci]].v_idct(block, + idct_tbl[p_jpeg->v_scale[sc]].v_idct(block, block + idct_cols); - idct_tbl[p_jpeg->h_scale[!!ci]].h_idct(block, b_out, - block + idct_rows * 8, b_width); + idct_tbl[p_jpeg->h_scale[sc]].h_idct(block, b_out, + block + idct_rows * 8, rowstep); +#endif +#ifndef HAVE_LCD_COLOR + if (ci == 2) + { /* luma from R, G and B (JFIF weights) */ + int w = BIT_N(p_jpeg->h_scale[0]); + int xi, yi; + for (yi = 0; yi < idct_rows; yi++, b_out += b_width) + for (xi = 0; xi < w; xi++) + b_out[xi] = (77 * rgb_tmp[0][yi * 8 + xi] + + 150 * rgb_tmp[1][yi * 8 + xi] + + 29 * b_out[xi] + 128) >> 8; + } #endif } } /* for blkn */ @@ -1965,12 +2019,8 @@ block_end: { /* if a restart marker is due: */ p_jpeg->restart = p_jpeg->restart_interval; /* count again */ search_restart(p_jpeg); /* align the bitstream */ -#ifdef HAVE_LCD_COLOR p_jpeg->last_dc_val[0] = p_jpeg->last_dc_val[1] = p_jpeg->last_dc_val[2] = 0; /* reset decoder */ -#else - p_jpeg->last_dc_val = 0; -#endif } } } /* if !p_jpeg->mcu_row */ @@ -2085,7 +2135,7 @@ int clip_jpeg_fd(int fd, int flags, #else p_jpeg->fd = fd; if (p_jpeg->len == 0) - p_jpeg->len = filesize(p_jpeg->fd); + p_jpeg->len = ffilesize(p_jpeg->fd); p_jpeg->read_buf = read_buf; p_jpeg->skip_bytes_seek = skip_bytes_seek; @@ -2176,10 +2226,16 @@ int clip_jpeg_fd(int fd, int flags, (p_jpeg->y_size << p_jpeg->v_scale[0]) >> 3 == bm->height) resize = false; #ifdef HAVE_LCD_COLOR - p_jpeg->h_scale[1] = p_jpeg->h_scale[0] + - p_jpeg->frameheader[0].horizontal_sampling - 1; - p_jpeg->v_scale[1] = p_jpeg->v_scale[0] + - p_jpeg->frameheader[0].vertical_sampling - 1; + { /* chroma blocks are scaled up by the luma:chroma sampling ratio */ + struct frame_component *chroma = + &p_jpeg->frameheader[p_jpeg->blocks > 1 ? 1 : 0]; + p_jpeg->h_scale[1] = p_jpeg->h_scale[0] + + p_jpeg->frameheader[0].horizontal_sampling - + chroma->horizontal_sampling; + p_jpeg->v_scale[1] = p_jpeg->v_scale[0] + + p_jpeg->frameheader[0].vertical_sampling - + chroma->vertical_sampling; + } JDEBUGF("chroma IDCT size: %dx%d\n", BIT_N(p_jpeg->h_scale[1]), BIT_N(p_jpeg->v_scale[1])); #endif @@ -2230,15 +2286,10 @@ int clip_jpeg_fd(int fd, int flags, buf_start += sizeof(struct jpeg); #endif maxsize = buf_end - buf_start; -#ifdef HAVE_LCD_COLOR - int decode_buf_size = (p_jpeg->x_mbl << p_jpeg->h_scale[1]) - << p_jpeg->v_scale[1]; -#else int decode_buf_size = (p_jpeg->x_mbl << p_jpeg->h_scale[0]) << p_jpeg->v_scale[0]; decode_buf_size <<= p_jpeg->frameheader[0].horizontal_sampling + p_jpeg->frameheader[0].vertical_sampling - 2; -#endif decode_buf_size *= JPEG_PIX_SZ; JDEBUGF("decode buffer size: %d\n", decode_buf_size); if (return_size) @@ -2274,7 +2325,8 @@ int clip_jpeg_fd(int fd, int flags, if (resize) { if (resize_on_load(bm, dither, &src_dim, &rset, buf_start, maxsize, - cformat, IF_PIX_FMT(p_jpeg->blocks == 1 ? 0 : 1,) store_row_jpeg, + cformat, IF_PIX_FMT(p_jpeg->blocks == 1 || p_jpeg->rgb ? 0 : 1,) + store_row_jpeg, p_jpeg)) return bm_size; } else { @@ -2298,7 +2350,7 @@ int clip_jpeg_fd(int fd, int flags, { part = store_row_jpeg(p_jpeg); #ifdef HAVE_LCD_COLOR - if (p_jpeg->blocks > 1) + if (p_jpeg->blocks > 1 && !p_jpeg->rgb) { struct uint8_rgb *qp = part->buf; struct uint8_rgb *end = qp + bm->width; diff --git a/apps/recorder/keyboard.c b/apps/recorder/keyboard.c index ee47470bb1..cb00fef74f 100644 --- a/apps/recorder/keyboard.c +++ b/apps/recorder/keyboard.c @@ -47,8 +47,23 @@ #define DEFAULT_MARGIN 6 #define KBD_BUF_SIZE 500 +/* Sentinel picker cell, drawn as an icon (the UI font has no glyph for it): + * KBD_SPACE inserts a space. */ +#define KBD_SPACE 0x2423 + #ifdef HAVE_TOUCHSCREEN +/* Increase grid size so it responds better to taps */ +#ifdef HAVE_KBD_POINT_MODE +#ifndef MIN_GRID_SIZE +#if defined(LCD_DPI) && (LCD_DPI > 0) +#define MIN_GRID_SIZE ((LCD_DPI * 50 + 127) / 254) +#else +#define MIN_GRID_SIZE 44 +#endif +#endif +#else #define MIN_GRID_SIZE 16 +#endif #define GRID_SIZE(s, x) \ ((s) == SCREEN_MAIN && MIN_GRID_SIZE > (x) ? MIN_GRID_SIZE: (x)) #endif @@ -389,6 +404,29 @@ static ucschar_t get_kbd_ch(struct keyboard_parameters *pm, int x, int y) k = k * pm->max_chars + x; return (*pbuf != 0xFEFF && k < *pbuf)? pbuf[k+1]: ' '; } + +#ifdef HAVE_TOUCHSCREEN +/* Number of real characters in the grid row drawn at picker row y. */ +static int get_kbd_row_len(struct keyboard_parameters *pm, int y) +{ + unsigned int n, i, k = pm->page*pm->lines + y; + ucschar_t *pbuf; + for (pbuf = pm->kbd_buf_ptr; (i = *pbuf) != 0xFEFF; pbuf += i + 1) + { + n = i ? (i + pm->max_chars - 1) / pm->max_chars : 1; + if (k < n) + { + int len = (int)i - (int)(k * pm->max_chars); + if (len > (int)pm->max_chars) + len = pm->max_chars; + return len < 0 ? 0 : len; + } + k -= n; + } + return 0; +} +#endif /* HAVE_TOUCHSCREEN */ + static void kbd_calc_pm_params(struct keyboard_parameters *pm, struct screen *sc, struct edit_state *state); static void kbd_calc_vp_params(struct keyboard_parameters *pm, @@ -430,10 +468,14 @@ int kbd_input(char* text, int buflen, ucschar_t *kbd) } #ifdef HAVE_TOUCHSCREEN + enum touchscreen_mode old_mode = touchscreen_get_mode(); +#ifdef HAVE_KBD_POINT_MODE + touchscreen_set_mode(TOUCHSCREEN_POINT); +#else /* keyboard is unusuable in pointing mode so force 3x3 for now. * TODO - fix properly by using a bigger font and changing the layout */ - enum touchscreen_mode old_mode = touchscreen_get_mode(); touchscreen_set_mode(TOUCHSCREEN_BUTTON); +#endif #endif /* initialize state */ state.text = text; @@ -458,6 +500,48 @@ int kbd_input(char* text, int buflen, ucschar_t *kbd) const unsigned char *p; int len = 0; +#ifdef HAVE_KBD_POINT_MODE + if (touchscreen_get_mode() == TOUCHSCREEN_POINT) + { +#if (LCD_WIDTH >= 480) && (LCD_HEIGHT >= 640) + /* Three pages of eight lines. Page two is page one shifted, cell + * for cell; page three completes Latin-1 and adds the accented + * letters common to the other European locales. The space bar + * occupies the same cells on every page. Ten columns need the + * width, and all eight lines have to be visible at once. */ + p = "1234567890\n" + "qwertyuiop\n" + "asdfghjkl;\n" + "zxcvbnm,./\n" + "àáâäåçèéêë\n" + "ìíîïñòóôöù\n" + "úûüÿ'æøðþý\n" + "`-=␣␣␣␣[]\\\n" + "!@#$%^&*()\n" + "QWERTYUIOP\n" + "ASDFGHJKL:\n" + "ZXCVBNM<>?\n" + "ÀÁÂÄÅÇÈÉÊË\n" + "ÌÍÎÏÑÒÓÔÖÙ\n" + "ÚÛÜŸ\"ÆØÐÞÝ\n" + "~_+␣␣␣␣{}|\n" + "¡¢£¤¥¦§¨©ª\n" + "«¬®¯°±²³´µ\n" + "¶·¸¹º»¼½¾¿\n" + "×÷ßÃãÕõ€Œœ\n" + "ĐđČčĎďĚěŇň\n" + "ŘřŠšŤťŮůŽž\n" + "ĄąĆćĘꣳŃń\n" + "ŚśŹ␣␣␣␣źŻż"; + pm->default_lines = 8; + pm->max_line_len = 10; +#else +# error "HAVE_KBD_POINT_MODE: no point-mode layout for this screen size" +#endif + } + else +#endif + { #if LCD_WIDTH >= 160 && LCD_HEIGHT >= 96 struct screen *sc = &screens[l]; @@ -528,6 +612,7 @@ int kbd_input(char* text, int buflen, ucschar_t *kbd) pm->max_line_len = 18; } #endif + } pbuf = pm->kbd_buf; while (*p) { @@ -849,6 +934,11 @@ static void kbd_calc_pm_params(struct keyboard_parameters *pm, (touchscreen_get_mode() == TOUCHSCREEN_POINT)); #endif +#ifdef HAVE_TOUCHSCREEN + if (touchscreen_get_mode() == TOUCHSCREEN_POINT) + pm->curfont = sc->getuifont(); + else +#endif pm->curfont = pm->default_lines ? FONT_SYSFIXED : sc->getuifont(); font = font_get(pm->curfont); pm->font_h = font->height; @@ -907,6 +997,11 @@ static void kbd_calc_vp_params(struct keyboard_parameters *pm, icon_w = get_icon_width(sc->screen_type); sc_w = vp->width; /**sc->getwidth();**/ +#ifdef HAVE_TOUCHSCREEN + if (touchscreen_get_mode() == TOUCHSCREEN_POINT) + pm->font_w = sc_w / pm->max_line_len; /* fill width, allow >10 cols */ + else +#endif if (pm->font_w < sc_w / pm->max_line_len) pm->font_w = sc_w / pm->max_line_len; pm->max_chars = sc_w / pm->font_w; @@ -1075,22 +1170,47 @@ static void kbd_draw_picker(struct keyboard_parameters *pm, { for (i = 0; i < pm->max_chars; i++) { + int cx = i*pm->font_w, cy = j*pm->font_h; ch = get_kbd_ch(pm, i, j); + if (ch == KBD_SPACE) + { + /* space bar: a bar with short upturned ends. Cells join; + * the run's first/last cell adds the vertical end tick. */ + int th = MAX(2, pm->font_h/14); + int by = cy + (pm->font_h*2)/3; + int tk = pm->font_h/6; + int rx = cx + pm->font_w - th; + sc->fillrect(cx, by, pm->font_w, th); + if (i == 0 || get_kbd_ch(pm, i-1, j) != KBD_SPACE) + sc->fillrect(cx, by - tk, th, tk + th); + if (get_kbd_ch(pm, i+1, j) != KBD_SPACE) + sc->fillrect(rx, by - tk, th, tk + th); + continue; + } utf8 = utf8encode(ch, outline); *utf8 = 0; sc->getstringsize(outline, &w, &h); - sc->putsxy(i*pm->font_w + (pm->font_w-w) / 2, - j*pm->font_h + (pm->font_h-h) / 2, outline); + sc->putsxy(cx + (pm->font_w-w) / 2, + cy + (pm->font_h-h) / 2, outline); } } if (!pm->line_edit) { - /* highlight the key that has focus */ + /* highlight the focused key; the space bar spans several cells and + * highlights as one */ + int hx = pm->x, hw = 1; + if (get_kbd_ch(pm, hx, pm->y) == KBD_SPACE) + { + while (hx > 0 && get_kbd_ch(pm, hx - 1, pm->y) == KBD_SPACE) + hx--; + while (get_kbd_ch(pm, hx + hw, pm->y) == KBD_SPACE) + hw++; + } sc->set_drawmode(DRMODE_COMPLEMENT); - sc->fillrect(pm->font_w*pm->x, pm->font_h*pm->y, - pm->font_w, pm->font_h); + sc->fillrect(pm->font_w*hx, pm->font_h*pm->y, + pm->font_w*hw, pm->font_h); sc->set_drawmode(DRMODE_SOLID); } } @@ -1337,8 +1457,8 @@ static void kbd_draw_buttons(struct keyboard_parameters *pm, struct screen *sc) /* draw buttons */ if (pm->pages > 1) { - /* button to flip page. */ - vp.y = pm->lines*pm->font_h; + /* button to flip page */ + vp.y = pm->kbd_viewports[eKBD_VP_PICKER].y + pm->lines*pm->font_h; sc->hline(0, sc_w - 1, 0); sc->putsxy(0, text_y, ">"); } @@ -1378,22 +1498,32 @@ static int keyboard_touchscreen(struct keyboard_parameters *pm, #endif if (x < 0 || y < 0) return ACTION_NONE; - if (y < pm->lines*pm->font_h) + /* The picker grid is drawn in its own viewport, below the edit line, so + * translate the absolute touch into that viewport (see kbd_draw_picker). */ + struct viewport *picker_vp = &pm->kbd_viewports[eKBD_VP_PICKER]; + int picker_x = x - picker_vp->x; + int picker_y = y - picker_vp->y; + if (picker_y >= 0 && picker_y < pm->lines*pm->font_h) { - if (x/pm->font_w < pm->max_chars) + int row = picker_y/pm->font_h; + /* ignore taps past the end of a short row */ + if (picker_x >= 0 && picker_x/pm->font_w < get_kbd_row_len(pm, row)) { /* picker area */ state->changed = CHANGED_PICKER; - pm->x = x/pm->font_w; - pm->y = y/pm->font_h; + pm->x = picker_x/pm->font_w; + pm->y = row; pm->line_edit = false; if (button == BUTTON_REL) return ACTION_KBD_SELECT; } } - else if (y < pm->main_y - pm->keyboard_margin) + else if (pm->pages > 1 && picker_y >= pm->lines*pm->font_h && + picker_y < pm->lines*pm->font_h + + GRID_SIZE(sc->screen_type, sc->getcharheight())) { - /* button to flip page */ + /* flip-page button, drawn just below the grid; test the same band in + * picker-relative coords so ">" is tappable. */ if (button == BUTTON_REL) return ACTION_KBD_PAGE_FLIP; } @@ -1432,6 +1562,9 @@ static void kbd_insert_selected(struct keyboard_parameters *pm, /* find input char */ ucschar_t ch = get_kbd_ch(pm, pm->x, pm->y); + if (ch == KBD_SPACE) + ch = ' '; + /* check for hangul input */ if (ch >= 0x3131 && ch <= 0x3163) { @@ -1552,6 +1685,18 @@ static void kbd_move_picker_horizontal(struct keyboard_parameters *pm, state->changed = CHANGED_PICKER; pm->x += dir; + /* The space bar spans several cells but highlights and inserts as one key, + * so step over the whole run instead of stopping inside it, which looks + * like the cursor is stuck. */ + if (pm->x >= 0 && pm->x < pm->max_chars && + get_kbd_ch(pm, pm->x, pm->y) == KBD_SPACE && + get_kbd_ch(pm, pm->x - dir, pm->y) == KBD_SPACE) + { + while (pm->x >= 0 && pm->x < pm->max_chars && + get_kbd_ch(pm, pm->x, pm->y) == KBD_SPACE) + pm->x += dir; + } + if (pm->x < 0) { if (!global_settings.list_wraparound && pm->page == 0) diff --git a/apps/recorder/peakmeter.c b/apps/recorder/peakmeter.c index aac1d3c077..24ee7f405e 100644 --- a/apps/recorder/peakmeter.c +++ b/apps/recorder/peakmeter.c @@ -492,6 +492,14 @@ void peak_meter_init_times(int release, int hold_ms, int clip_hold_sec) pm_clip_hold = HZ * clip_hold_sec; } +/* The release, in pixels per tick, and the peak hold time in ticks, as set + * by peak_meter_init_times(): for peak meters drawn elsewhere */ +void peak_meter_get_times(int *release, int *hold_ticks) +{ + *release = pm_peak_release; + *hold_ticks = pm_peak_hold; +} + #ifdef HAVE_RECORDING /** * Enable/disable clip counting diff --git a/apps/recorder/peakmeter.h b/apps/recorder/peakmeter.h index 8c0d0d1486..a24d64bc21 100644 --- a/apps/recorder/peakmeter.h +++ b/apps/recorder/peakmeter.h @@ -44,6 +44,7 @@ extern void peak_meter_set_clip_hold(int time); extern void peak_meter_peek(void); extern void peak_meter_init_range( bool dbfs, int range_min, int range_max); extern void peak_meter_init_times(int release, int hold_ms, int clip_hold_sec); +extern void peak_meter_get_times(int *release, int *hold_ticks); #ifdef HAVE_AGC extern void peak_meter_get_peakhold(int *peak_left, int *peak_right); #endif diff --git a/apps/recorder/recording.c b/apps/recorder/recording.c index 4f7ccc1be9..1a8058b089 100644 --- a/apps/recorder/recording.c +++ b/apps/recorder/recording.c @@ -1540,6 +1540,9 @@ bool recording_screen(bool no_source) { FOR_NB_SCREENS(i) screens[i].set_viewport(NULL); + /* not monitoring the input in USB mode: this screen + * only switches back to playback when it ends */ + rec_set_source(AUDIO_SRC_PLAYBACK, SRCF_PLAYBACK); default_event_handler(SYS_USB_CONNECTED); done = true; rec_status |= RCSTAT_BEEN_IN_USB_MODE; diff --git a/apps/root_menu.c b/apps/root_menu.c index a546432d85..918669b245 100644 --- a/apps/root_menu.c +++ b/apps/root_menu.c @@ -68,6 +68,7 @@ #endif #ifdef HAVE_TAGCACHE #include "tagcache.h" +#include "tagtree.h" #endif #include "language.h" #include "plugin.h" @@ -269,6 +270,12 @@ static int browser(void* param) } if (!tagcache_is_usable()) return GO_TO_PREVIOUS; + if (!tagtree_ready()) + { + splashf(HZ*3, "%s\n%s", str(LANG_FILE_NOT_FOUND), + ROCKBOX_DIR "/tagnavi.config"); + return GO_TO_PREVIOUS; + } filter = SHOW_ID3DB; last_ft_dirlevel = tc->dirlevel; tc->dirlevel = last_db_dirlevel; @@ -1032,10 +1039,7 @@ void root_menu(void) } } - - push_activity_without_refresh(ACTIVITY_UNKNOWN); /* prevent plugin_load */ - next_screen = load_plugin_screen(key); /* from flashing root */ - pop_current_activity_without_refresh(); /* menu activity */ + next_screen = load_plugin_screen(key); if (next_screen == GO_TO_PREVIOUS) { diff --git a/apps/settings.c b/apps/settings.c index afe62299e3..93e278161c 100644 --- a/apps/settings.c +++ b/apps/settings.c @@ -34,6 +34,9 @@ #include "crc32.h" #include "sound.h" #include "settings.h" +#ifdef HAVE_COMPOSITE_VIDEO_OUT +#include "videoout.h" +#endif #include "debug.h" #include "usb.h" #include "backlight.h" @@ -145,6 +148,8 @@ static void rename_temp_file(const char *tempfile, } #endif /* ndef ROCKBOX_NO_TEMP_SETTINGS_FILE */ +#endif // !__PCTOOL__ + const char* setting_get_cfgvals(const struct settings_list *setting) { if ((setting->flags & F_TABLE_SETTING) == F_TABLE_SETTING) @@ -161,6 +166,8 @@ const char* setting_get_cfgvals(const struct settings_list *setting) return NULL; } +#ifndef __PCTOOL__ + /* calculates and stores crc of settings, returns true if settings have changed */ static bool settings_crc_changed(void) { @@ -208,6 +215,8 @@ void settings_load(void) settings_crc_changed(); } +#endif // !__PCTOOL__ + bool cfg_string_to_int(const struct settings_list *setting, int* out, const char* str) { const char* ptr = setting_get_cfgvals(setting); @@ -393,6 +402,7 @@ bool string_to_cfg(const char *name, char* value, bool *theme_changed) return true; } +#ifndef __PCTOOL__ bool settings_load_config(const char* file, bool apply) { logf("%s()\r\n", __func__); @@ -873,6 +883,18 @@ void sound_settings_apply(void) #endif } +#ifdef HAVE_COMPOSITE_VIDEO_OUT +void settings_apply_videoout(int mode) +{ + if (mode < VIDEOOUT_OFF || mode > VIDEOOUT_ON) + mode = VIDEOOUT_OFF; + + global_settings.composite_video_output = mode; + videoout_set_mode((enum videoout_mode)mode, FBADDR(0, 0), + LCD_WIDTH, LCD_HEIGHT); +} +#endif + void settings_apply(bool read_disk) { logf("%s", __func__); @@ -1101,6 +1123,10 @@ void settings_apply(bool read_disk) #endif #endif /* HAVE_BACKLIGHT */ +#ifdef HAVE_COMPOSITE_VIDEO_OUT + settings_apply_videoout(global_settings.composite_video_output); +#endif + #ifndef HAS_BUTTON_HOLD set_selective_softlock_actions( global_settings.bt_selective_softlock_actions, @@ -1128,6 +1154,9 @@ void settings_apply(bool read_disk) #if !defined(SIMULATOR) && !defined(USB_NONE) && (defined(HAVE_USB_ADB) || defined(HAVE_USB_POWER)) usb_set_mode(global_settings.usb_mode); #endif +#if defined(USB_ENABLE_AUDIO) || defined(HAVE_HOST_USB_AUDIO) + usb_set_audio(global_settings.usb_audio); +#endif #if defined(HAVE_GENERAL_PURPOSE_LED) led_hw_on(); #endif diff --git a/apps/settings.h b/apps/settings.h index 178817f280..5a0f4f83e3 100644 --- a/apps/settings.h +++ b/apps/settings.h @@ -31,6 +31,7 @@ #include "button.h" #include "audio.h" #include "dsp_proc_settings.h" +#include "gui/list.h" struct opt_items { unsigned const char* string; @@ -124,7 +125,8 @@ enum { SINGLE_MODE_ARTIST, SINGLE_MODE_COMPOSER, SINGLE_MODE_GROUPING, - SINGLE_MODE_GENRE + SINGLE_MODE_GENRE, + SINGLE_MODE_PLAYLIST, }; enum @@ -289,6 +291,9 @@ void settings_apply_skins(void); void settings_apply(bool read_disk); void settings_apply_pm_range(void); +#ifdef HAVE_COMPOSITE_VIDEO_OUT +void settings_apply_videoout(int mode); +#endif void settings_display(void); enum optiontype { RB_INT, RB_BOOL }; @@ -746,6 +751,9 @@ struct user_settings #ifdef HAVE_BACKLIGHT_BRIGHTNESS int brightness; #endif +#ifdef HAVE_COMPOSITE_VIDEO_OUT + int composite_video_output; +#endif #ifdef HAVE_REMOTE_LCD /* remote lcd */ @@ -797,7 +805,12 @@ struct user_settings #ifdef HAVE_TOUCHSCREEN int touch_mode; + bool touch_enable_flick_shortcuts; struct touchscreen_parameter ts_calibration_data; + struct list_kinetic_scroll_settings kinetic_scroll_accel; + struct list_kinetic_scroll_settings kinetic_scroll_brake; + struct list_kinetic_scroll_settings kinetic_scroll_decel; + struct list_kinetic_scroll_settings kinetic_scroll_press; #endif #ifdef HAVE_PITCHCONTROL @@ -814,7 +827,7 @@ struct user_settings int usb_keypad_mode; #endif -#ifdef USB_ENABLE_AUDIO +#if defined(USB_ENABLE_AUDIO) || defined(HAVE_HOST_USB_AUDIO) int usb_audio; #endif diff --git a/apps/settings_list.c b/apps/settings_list.c index 1f24a8e0af..17f22dfd11 100644 --- a/apps/settings_list.c +++ b/apps/settings_list.c @@ -32,6 +32,9 @@ #include "sound.h" #include "pcm_sink.h" #include "settings.h" +#ifdef HAVE_COMPOSITE_VIDEO_OUT +#include "videoout.h" +#endif #include "rbpaths.h" #include "settings_list.h" #include "usb.h" @@ -42,6 +45,9 @@ #include "open_plugin.h" #include "misc.h" #include "playback.h" +#ifndef __PCTOOL__ +#include "fixedpoint.h" +#endif #ifdef HAVE_REMOTE_LCD #include "lcd-remote.h" #endif @@ -420,7 +426,7 @@ static const char graphic_numeric[] = "graphic,numeric"; # define DEFAULT_REC_MIC_GAIN 0 # define DEFAULT_REC_LEFT_GAIN 0 # define DEFAULT_REC_RIGHT_GAIN 0 -# elif defined(HAVE_WM8975) +# elif defined(HAVE_WM8975) || defined(HAVE_WM1870) # define DEFAULT_REC_MIC_GAIN 16 # define DEFAULT_REC_LEFT_GAIN 0 # define DEFAULT_REC_RIGHT_GAIN 0 @@ -676,16 +682,16 @@ static int32_t get_precut_talkid(int value, int unit) #endif /* __PCTOOL__ */ struct eq_band_setting eq_defaults[EQ_NUM_BANDS] = { - { 32, 7, 0 }, - { 64, 10, 0 }, - { 125, 10, 0 }, - { 250, 10, 0 }, - { 500, 10, 0 }, - { 1000, 10, 0 }, - { 2000, 10, 0 }, - { 4000, 10, 0 }, - { 8000, 10, 0 }, - { 16000, 7, 0 }, + { EQ_FILTER_LOW_SHELF, 32, 7, 0 }, + { EQ_FILTER_PEAK, 64, 10, 0 }, + { EQ_FILTER_PEAK, 125, 10, 0 }, + { EQ_FILTER_PEAK, 250, 10, 0 }, + { EQ_FILTER_PEAK, 500, 10, 0 }, + { EQ_FILTER_PEAK, 1000, 10, 0 }, + { EQ_FILTER_PEAK, 2000, 10, 0 }, + { EQ_FILTER_PEAK, 4000, 10, 0 }, + { EQ_FILTER_PEAK, 8000, 10, 0 }, + { EQ_FILTER_HIGH_SHELF, 16000, 7, 0 }, }; static const int wps_context_menu_default = @@ -704,7 +710,7 @@ static const int tree_hotkey_default = HOTKEY_OFF; #endif #ifndef __PCTOOL__ -static void eq_load_from_cfg(void *setting, char *value) +static void eq_load_from_cfg(void *setting, char *value, bool new) { struct eq_band_setting *eq = setting; char *val_end, *end; @@ -728,14 +734,68 @@ static void eq_load_from_cfg(void *setting, char *value) /* gain */ value = end + 1; if (value > val_end) return; + if (new) + { + end = strchr(value, ','); + if (!end) return; + } eq->gain = atoi(value); + + if (!new) return; + + /* type */ + value = end + 1; + if (value > val_end) return; + value = skip_whitespace(value); + if (strcasecmp(value, "LOW_SHELF") == 0) + eq->type = EQ_FILTER_LOW_SHELF; + else if (strcasecmp(value, "PEAK") == 0) + eq->type = EQ_FILTER_PEAK; + else if (strcasecmp(value, "HIGH_SHELF") == 0) + eq->type = EQ_FILTER_HIGH_SHELF; +} + +static void eq_load_from_cfg_old_low_shelf(void *setting, char *value) +{ + eq_load_from_cfg(setting, value, false); + ((struct eq_band_setting *)setting)->type = EQ_FILTER_LOW_SHELF; +} + +static void eq_load_from_cfg_old_peak(void *setting, char *value) +{ + eq_load_from_cfg(setting, value, false); + ((struct eq_band_setting *)setting)->type = EQ_FILTER_PEAK; +} + +static void eq_load_from_cfg_old_high_shelf(void *setting, char *value) +{ + eq_load_from_cfg(setting, value, false); + ((struct eq_band_setting *)setting)->type = EQ_FILTER_HIGH_SHELF; +} + +static void eq_load_from_cfg_new(void *setting, char *value) +{ + eq_load_from_cfg(setting, value, true); } static char* eq_write_to_cfg(void *setting, char *buf, int buf_len) { struct eq_band_setting *eq = setting; - snprintf(buf, buf_len, "%d, %d, %d", eq->cutoff, eq->q, eq->gain); + const char *type = "PEAK"; + switch (eq->type) + { + case EQ_FILTER_LOW_SHELF: + type = "LOW_SHELF"; + break; + case EQ_FILTER_PEAK: + type = "PEAK"; + break; + case EQ_FILTER_HIGH_SHELF: + type = "HIGH_SHELF"; + break; + } + snprintf(buf, buf_len, "%d, %d, %d, %s", eq->cutoff, eq->q, eq->gain, type); return buf; } @@ -896,6 +956,77 @@ static void tsc_set_default(void* setting, void* defaultval) { memcpy(setting, defaultval, sizeof(struct touchscreen_parameter)); } + +static void list_kinetic_load_from_cfg(void *setting, char *value) +{ + struct list_kinetic_scroll_settings *param = setting; + long vals[4] = { 0, 0, 0 }; + int count = 0; + + while (*value && count < 3) + { + while (isspace (*value)) + value++; + + int num = atoi(value); + while (!isspace(*value)) + value++; + + if (count == 2) + /* delay in milliseconds */ + vals[count] = HZ * num / 1000; + else + { + /* polynomial coefficients in 1/100th scale */ + int sign = 1; + if (num < 0) + { + num = -num; + sign = -1; + } + + int frac = num % 100; + num /= 100; + + num <<= LIST_KINETIC_FRACBITS; + frac <<= LIST_KINETIC_FRACBITS; + vals[count] = num + fp_div(frac, 100, LIST_KINETIC_FRACBITS); + vals[count] *= sign; + } + + count++; + } + + param->a1 = vals[0]; + param->a0 = vals[1]; + param->delay = vals[2]; +} + +static char *list_kinetic_write_to_cfg(void *setting, char *buf, int buf_len) +{ + struct list_kinetic_scroll_settings *param = setting; + long vals[2] = { param->a1, param->a0 }; + + for (int i = 0; i < 2; ++i) + { + long num = vals[i] >> LIST_KINETIC_FRACBITS; + long frac = vals[i] - (num << LIST_KINETIC_FRACBITS); + vals[i] = (num * 100) + (frac * 100) / (1 << LIST_KINETIC_FRACBITS); + } + + snprintf(buf, buf_len, "%ld %ld %ld", + vals[0], vals[1], 1000 * param->delay / HZ); + return buf; +} + +static bool list_kinetic_is_default(void *setting, void *defaultval) +{ + return memcmp(setting, defaultval, sizeof(struct list_kinetic_scroll_settings)) != 0; +} +static void list_kinetic_set_default(void *setting, void *defaultval) +{ + memcpy(setting, defaultval, sizeof(struct list_kinetic_scroll_settings)); +} #endif static void start_in_callback(int var) @@ -1175,6 +1306,12 @@ const struct settings_list settings[] = { #ifdef HAVE_LCD_FLIP OFFON_SETTING(0, flip_display, LANG_FLIP_DISPLAY, false, "flip display", NULL), +#endif +#ifdef HAVE_COMPOSITE_VIDEO_OUT + CHOICE_SETTING(0, composite_video_output, LANG_COMPOSITE_VIDEO_OUTPUT, + VIDEOOUT_OFF, "composite video output", "off,auto,on", + settings_apply_videoout, 3, + ID2P(LANG_OFF), ID2P(LANG_AUTO), ID2P(LANG_ON)), #endif /* display */ CHOICE_SETTING(F_TEMPVAR|F_THEMESETTING, cursor_style, LANG_INVERT_CURSOR, @@ -1471,8 +1608,8 @@ const struct settings_list settings[] = { OFFON_SETTING(0,play_selected,LANG_PLAY_SELECTED,true,"play selected",NULL), CHOICE_SETTING(0, single_mode, LANG_SINGLE_MODE, 0, "single mode", - "off,track,album,album artist,artist,composer,work,genre", - NULL, 8, + "off,track,album,album artist,artist,composer,work,genre,playlist", + NULL, 9, ID2P(LANG_OFF), ID2P(LANG_TRACK), ID2P(LANG_ID3_ALBUM), @@ -1480,7 +1617,9 @@ const struct settings_list settings[] = { ID2P(LANG_ID3_ARTIST), ID2P(LANG_ID3_COMPOSER), ID2P(LANG_ID3_GROUPING), - ID2P(LANG_ID3_GENRE)), + ID2P(LANG_ID3_GENRE), + ID2P(LANG_PLAYLIST) + ), OFFON_SETTING(0,party_mode,LANG_PARTY_MODE,false,"party mode",NULL), OFFON_SETTING(0,fade_on_stop,LANG_FADE_ON_STOP,true,"volume fade",NULL), INT_SETTING(F_TIME_SETTING, ff_rewind_min_step, LANG_FFRW_STEP, 1, @@ -1833,21 +1972,39 @@ const struct settings_list settings[] = { "eq precut", UNIT_DB, 0, 240, 1, eq_precut_format, get_precut_talkid, dsp_set_eq_precut), + +#define EQ_BAND_OLD(id, string, type) \ + CUSTOM_SETTING(F_EQSETTING|F_DEPRECATED, eq_band_settings[id], -1, \ + &eq_defaults[id], string, \ + eq_load_from_cfg_old_##type, eq_write_to_cfg, \ + eq_is_changed, eq_set_default) + EQ_BAND_OLD(0, "eq low shelf filter", low_shelf), + EQ_BAND_OLD(1, "eq peak filter 1", peak), + EQ_BAND_OLD(2, "eq peak filter 2", peak), + EQ_BAND_OLD(3, "eq peak filter 3", peak), + EQ_BAND_OLD(4, "eq peak filter 4", peak), + EQ_BAND_OLD(5, "eq peak filter 5", peak), + EQ_BAND_OLD(6, "eq peak filter 6", peak), + EQ_BAND_OLD(7, "eq peak filter 7", peak), + EQ_BAND_OLD(8, "eq peak filter 8", peak), + EQ_BAND_OLD(9, "eq high shelf filter", high_shelf), +#undef EQ_BAND_OLD + #define EQ_BAND(id, string) \ CUSTOM_SETTING(F_EQSETTING, eq_band_settings[id], -1, \ &eq_defaults[id], string, \ - eq_load_from_cfg, eq_write_to_cfg, \ + eq_load_from_cfg_new, eq_write_to_cfg, \ eq_is_changed, eq_set_default) - EQ_BAND(0, "eq low shelf filter"), - EQ_BAND(1, "eq peak filter 1"), - EQ_BAND(2, "eq peak filter 2"), - EQ_BAND(3, "eq peak filter 3"), - EQ_BAND(4, "eq peak filter 4"), - EQ_BAND(5, "eq peak filter 5"), - EQ_BAND(6, "eq peak filter 6"), - EQ_BAND(7, "eq peak filter 7"), - EQ_BAND(8, "eq peak filter 8"), - EQ_BAND(9, "eq high shelf filter"), + EQ_BAND(0, "eq filter 0"), + EQ_BAND(1, "eq filter 1"), + EQ_BAND(2, "eq filter 2"), + EQ_BAND(3, "eq filter 3"), + EQ_BAND(4, "eq filter 4"), + EQ_BAND(5, "eq filter 5"), + EQ_BAND(6, "eq filter 6"), + EQ_BAND(7, "eq filter 7"), + EQ_BAND(8, "eq filter 8"), + EQ_BAND(9, "eq filter 9"), #undef EQ_BAND /* dithering */ @@ -2275,10 +2432,29 @@ const struct settings_list settings[] = { CHOICE_SETTING(0, touch_mode, LANG_TOUCHSCREEN_MODE, DEFAULT_TOUCHSCREEN_MODE, "touchscreen mode", "point,grid", NULL, 2, ID2P(LANG_TOUCHSCREEN_POINT), ID2P(LANG_TOUCHSCREEN_GRID)), + CHOICE_SETTING(0, touch_enable_flick_shortcuts, LANG_TOUCHSCREEN_FLICK_SHORTCUTS, true, + "touchscreen flick shortcuts", "off,on", NULL, + 2, ID2P(LANG_OFF), ID2P(LANG_ON)), CUSTOM_SETTING(0, ts_calibration_data, -1, &default_calibration_parameters, "touchscreen calibration", tsc_load_from_cfg, tsc_write_to_cfg, tsc_is_changed, tsc_set_default), + CUSTOM_SETTING(0, kinetic_scroll_accel, -1, + &list_kinetic_scroll_accel_default, "kinetic scroll accel", + list_kinetic_load_from_cfg, list_kinetic_write_to_cfg, + list_kinetic_is_default, list_kinetic_set_default), + CUSTOM_SETTING(0, kinetic_scroll_brake, -1, + &list_kinetic_scroll_brake_default, "kinetic scroll brake", + list_kinetic_load_from_cfg, list_kinetic_write_to_cfg, + list_kinetic_is_default, list_kinetic_set_default), + CUSTOM_SETTING(0, kinetic_scroll_decel, -1, + &list_kinetic_scroll_decel_default, "kinetic scroll decel", + list_kinetic_load_from_cfg, list_kinetic_write_to_cfg, + list_kinetic_is_default, list_kinetic_set_default), + CUSTOM_SETTING(0, kinetic_scroll_press, -1, + &list_kinetic_scroll_press_default, "kinetic scroll press", + list_kinetic_load_from_cfg, list_kinetic_write_to_cfg, + list_kinetic_is_default, list_kinetic_set_default), #endif OFFON_SETTING(0, prevent_skip, LANG_PREVENT_SKIPPING, false, "prevent track skip", NULL), OFFON_SETTING(0, rewind_across_tracks, LANG_REWIND_ACROSS_TRACKS, false, "rewind across tracks", NULL), @@ -2310,7 +2486,7 @@ const struct settings_list settings[] = { ), /* CHOICE_SETTING( usb_keypad_mode ) */ #endif -#ifdef USB_ENABLE_AUDIO +#if defined(USB_ENABLE_AUDIO) || defined(HAVE_HOST_USB_AUDIO) CHOICE_SETTING(0, usb_audio, LANG_USB_DAC, 0, "usb-dac", "never,always,while_charge_only,while_mass_storage", usb_set_audio, 4, ID2P(LANG_NEVER), ID2P(LANG_ALWAYS), ID2P(LANG_WHILE_USB_CHARGE_ONLY), ID2P(LANG_WHILE_MASS_STORAGE_USB_ONLY)), #endif diff --git a/apps/tagcache.c b/apps/tagcache.c index d5c8f79a7a..e33bc9330e 100644 --- a/apps/tagcache.c +++ b/apps/tagcache.c @@ -3096,7 +3096,7 @@ static int build_index(int index_type, struct tagcache_header *h, int tmpfd) */ masterfd_pos = lseek(masterfd, tcmh.tch.entry_count * sizeof(struct index_entry), SEEK_CUR); - if (masterfd_pos == filesize(masterfd)) + if (masterfd_pos == ffilesize(masterfd)) { logf("appending..."); init = true; diff --git a/apps/tagtree.c b/apps/tagtree.c index 83aa2fe21e..89889dfd8d 100644 --- a/apps/tagtree.c +++ b/apps/tagtree.c @@ -1467,9 +1467,16 @@ static bool initialize_tagtree(void) /* also used when user selects 'Reload' in return true; } +static bool tagnavi_loaded; + void tagtree_init(void) { - initialize_tagtree(); + tagnavi_loaded = initialize_tagtree(); +} + +bool tagtree_ready(void) +{ + return tagnavi_loaded; } static int format_str(struct tagcache_search *tcs, struct display_format *fmt, @@ -2097,7 +2104,8 @@ int tagtree_load(struct tree_context* c) { splash(HZ, ID2P(LANG_WAIT)); tagtree_unload(c); - if (!initialize_tagtree()) + tagnavi_loaded = initialize_tagtree(); + if (!tagnavi_loaded) return 0; } c->dirlevel = 0; diff --git a/apps/tagtree.h b/apps/tagtree.h index 8b24dd162a..b96b4fdf82 100644 --- a/apps/tagtree.h +++ b/apps/tagtree.h @@ -33,6 +33,7 @@ int tagtree_export(void); int tagtree_import(void); void tagtree_init(void) INIT_ATTR; +bool tagtree_ready(void); int tagtree_enter(struct tree_context* c, bool is_visible); void tagtree_exit(struct tree_context* c, bool is_visible); int tagtree_load(struct tree_context* c); diff --git a/apps/talk.c b/apps/talk.c index 9aaa93e70f..a353eadc6c 100644 --- a/apps/talk.c +++ b/apps/talk.c @@ -1117,7 +1117,7 @@ static int _talk_file(const char* filename, { return 0; } - size = filesize(fd); + size = ffilesize(fd); if (size > 0) { diff --git a/apps/tree.c b/apps/tree.c index 7f3bee1113..ba8aa96c2b 100644 --- a/apps/tree.c +++ b/apps/tree.c @@ -570,7 +570,7 @@ void resume_directory(const char *dir) /* Returns the current working directory and also writes cwd to buf if non-NULL. In case of error, returns NULL. */ -#ifdef CTRU +#ifdef N3DS char *__wrap_getcwd(char *buf, getcwd_size_t size) #else char *getcwd(char *buf, getcwd_size_t size) @@ -871,12 +871,13 @@ static int dirbrowse(void) #ifdef HAVE_QUICKSCREEN case ACTION_STD_QUICKSCREEN: { + /* Shortcuts Menu currently disabled in browse filter modes */ bool enter_shortcuts_menu = global_settings.shortcuts_replaces_qs; if (enter_shortcuts_menu && *tc.dirfilter >= NUM_FILTER_MODES) break; else if (!enter_shortcuts_menu) { - int ret = quick_screen_quick(button); + int ret = quickscreen_show(button); if (ret == QUICKSCREEN_IN_USB) reload_dir = true; else if (ret == QUICKSCREEN_GOTO_SHORTCUTS_MENU) @@ -893,13 +894,6 @@ static int dirbrowse(void) else return exit_to_new_screen(shortcut_ret); } - else if (enter_shortcuts_menu) /* currently disabled */ - { - /* QuickScreen defers skin updates, popping its activity, when - switching to Shortcuts Menu, so make up for that here: */ - FOR_NB_SCREENS(i) - skin_update(CUSTOM_STATUSBAR, i, SKIN_REFRESH_ALL); - } restore = do_restore_display; break; diff --git a/apps/tree.h b/apps/tree.h index cf8b4833c1..7d646ffed5 100644 --- a/apps/tree.h +++ b/apps/tree.h @@ -127,7 +127,7 @@ void tree_unlock_cache(struct tree_context *t); #else #define getcwd_size_t size_t #endif -#ifdef CTRU +#ifdef N3DS /* devkitarm already defines getcwd */ char *__wrap_getcwd(char *buf, getcwd_size_t size); #else diff --git a/backdrops/cabbiev2.360x640x16.bmp b/backdrops/cabbiev2.360x640x16.bmp new file mode 100644 index 0000000000..4b527dbf88 Binary files /dev/null and b/backdrops/cabbiev2.360x640x16.bmp differ diff --git a/bootloader/SOURCES b/bootloader/SOURCES index 8510479739..a56a3999b8 100644 --- a/bootloader/SOURCES +++ b/bootloader/SOURCES @@ -60,7 +60,7 @@ mpio_hd200_hd300.c nwz_linux.c #elif defined(HIBY_LINUX) || defined(FIIO_M3K_LINUX) hibyos_linux.c -#elif defined(RK27_GENERIC) || defined(HM60X) || defined(HM801) \ +#elif defined(RK27_GENERIC) || defined(SAMSUNG_YPCP3) || defined(HM60X) || defined(HM801) \ || defined(MA9) || defined(MA9C) || defined(MA8) || defined(MA8C) \ || defined(IHIFI760) || defined(IHIFI960) || defined(IHIFI800) \ || defined(IHIFI770) || defined(IHIFI770C) diff --git a/bootloader/common.c b/bootloader/common.c index 705441357a..b07d419861 100644 --- a/bootloader/common.c +++ b/bootloader/common.c @@ -153,7 +153,7 @@ int load_raw_firmware(unsigned char* buf, char* firmware, int buffer_size) return EFILE_NOT_FOUND; } - len = filesize(fd); + len = ffilesize(fd); if (len > buffer_size) return EFILE_TOO_BIG; diff --git a/bootloader/gigabeat-s.c b/bootloader/gigabeat-s.c index b0c9b54f4d..42943111be 100644 --- a/bootloader/gigabeat-s.c +++ b/bootloader/gigabeat-s.c @@ -151,7 +151,7 @@ static void untar(int tar_fd) char path[102]; int fd, i; int ret; - size_t size = filesize(tar_fd); + size_t size = ffilesize(tar_fd); if (size > tarbuf_size) { @@ -159,7 +159,7 @@ static void untar(int tar_fd) return; } - ret = read(tar_fd, tarbuf, filesize(tar_fd)); + ret = read(tar_fd, tarbuf, ffilesize(tar_fd)); if (ret < 0) { printf("couldn't read tar file (%d)", ret); diff --git a/bootloader/hibyos_linux.c b/bootloader/hibyos_linux.c index edda204796..f42070d001 100644 --- a/bootloader/hibyos_linux.c +++ b/bootloader/hibyos_linux.c @@ -36,6 +36,7 @@ #include "lcd.h" #include "font.h" #include "power.h" +#include "rbpaths.h" #include #include #include @@ -126,16 +127,23 @@ #define BUTTON_LEFT BUTTON_VOL_DOWN #define BUTTON_RIGHT BUTTON_VOL_UP #include "bitmaps/hibyicon.h" +#elif defined(HIDIZS_AP80MAX) +#define ICON_WIDTH 130 +#define ICON_HEIGHT 130 +#define ICON_NAME bm_hibyicon +#define OF_NAME "HIBY PLAYER" +#define BUTTON_UP BUTTON_PREV +#define BUTTON_DOWN BUTTON_NEXT +#define BUTTON_SELECT BUTTON_PLAY +/* No volume keys, so the scroll wheel takes left/right */ +#define BUTTON_LEFT BUTTON_SCROLL_BACK +#define BUTTON_RIGHT BUTTON_SCROLL_FWD +#include "bitmaps/hibyicon.h" #else #error "must define ICON_WIDTH/HEIGHT" #endif #define BASE_DIR PIVOT_ROOT -//#ifdef FIIO_M3K_LINUX -//#define BASE_DIR "/mnt" -//#else -//#define BASE_DIR "/mnt/sd_0" -//#endif /* images */ #include "bitmaps/rockboxicon.h" @@ -246,7 +254,7 @@ static void mount_storage(int enable) static enum boot_mode load_boot_mode(enum boot_mode mode) { mount_storage(true); - int fd = open(BASE_DIR "/.rockbox/rb_bl_mode.txt", O_RDONLY); + int fd = open(BASE_DIR ROCKBOX_DIR "/rb_bl_mode.txt", O_RDONLY); if(fd >= 0) { read(fd, &mode, sizeof(mode)); @@ -258,7 +266,7 @@ static enum boot_mode load_boot_mode(enum boot_mode mode) static void save_boot_mode(enum boot_mode mode) { mount_storage(true); - int fd = open(BASE_DIR "/.rockbox/rb_bl_mode.txt", O_RDWR | O_CREAT | O_TRUNC); + int fd = open(BASE_DIR ROCKBOX_DIR "/rb_bl_mode.txt", O_RDWR | O_CREAT | O_TRUNC); if(fd >= 0) { write(fd, &mode, sizeof(mode)); @@ -279,7 +287,7 @@ static enum boot_mode get_boot_mode(void) while(true) { /* on usb detect, immediately boot with last choice */ -#if !defined(HIBY_R3PROII) && !defined(HIBY_R1) +#if !defined(HIBY_R3PROII) && !defined(HIBY_R1) && !defined(HIDIZS_AP80MAX) if(!adb_running && power_input_status() & POWER_INPUT_USB_CHARGER) { /* save last choice */ @@ -455,9 +463,12 @@ int choice_screen(const char *title, bool center, int nr_choices, const char *ch void run_file(const char *name) { - char *dirname = BASE_DIR; - char *buf = malloc(strlen(dirname) + strlen(name) + 1); - sprintf(buf, "%s%s", dirname, name); + const char *dirname = BASE_DIR; + size_t path_size = strlen(dirname) + strlen(name) + 2; + char *buf = malloc(path_size); + if (!buf) + return; + snprintf(buf, path_size, "%s/%s", dirname, name); lcd_clear_display(); lcd_set_foreground(LCD_RGBPACK(255, 201, 0)); @@ -472,6 +483,7 @@ void run_file(const char *name) } int status; waitpid(pid, &status, 0); + free(buf); if(WIFEXITED(status)) { lcd_set_foreground(LCD_RGBPACK(255, 201, 0)); @@ -526,7 +538,7 @@ static void adb(int start) pid_t pid = fork(); if(pid == 0) { -#if defined(HIBY_R3PROII) || defined(HIBY_R1) +#if defined(HIBY_R3PROII) || defined(HIBY_R1) || defined(HIDIZS_AP80MAX) execlp("/etc/init.d/T90adb", "T90adb", start ? "start" : "stop", NULL); #else execlp("/etc/init.d/K90adb", "K90adb", start ? "start" : "stop", NULL); @@ -694,20 +706,24 @@ int main(int argc, char **argv) } else if(mode == BOOT_ROCKBOX) { -#if defined(HIBY_R3PROII) || defined(HIBY_R1) +#if defined(HIBY_R3PROII) || defined(HIBY_R1) || defined(HIDIZS_AP80MAX) /* Suspend bluetooth as it's not currently supported */ system("/usr/bin/bt_suspend"); +#endif +#if defined(HIDIZS_AP80MAX) + /* Hidizs bt_suspend has these commented out */ + system("killall bluealsa bt-agent bluetoothd dbus-daemon 2>/dev/null"); #endif fflush(stdout); mount_storage(true); - system("/bin/cp " BASE_DIR "/.rockbox/" BOOTFILE " /tmp"); + system("/bin/cp " BASE_DIR ROCKBOX_DIR "/" BOOTFILE " /tmp"); system("/bin/chmod +x /tmp/" BOOTFILE); #ifdef HIBY_R1 /* Load libasound from Rockbox directory */ char *argvr1[] = { "/tmp/" BOOTFILE, 0 }; char *envpr1[] = { - "LD_LIBRARY_PATH=/data/mnt/sd_0/.rockbox", + "LD_LIBRARY_PATH=" BASE_DIR "/" ROCKBOX_DIR, 0 }; execve(argvr1[0], &argvr1[0], envpr1); diff --git a/bootloader/ipod-s5l87xx.c b/bootloader/ipod-s5l87xx.c index 077fa1d3e6..ef97a7dfb6 100644 --- a/bootloader/ipod-s5l87xx.c +++ b/bootloader/ipod-s5l87xx.c @@ -45,6 +45,12 @@ #include "version.h" #include "powermgmt.h" #include "usb.h" +#ifdef IPOD_NANO3G +#include "nand-target.h" +#endif +#ifdef NAND_CHECK +#include "string-extra.h" +#endif #ifdef HAVE_SERIAL #include "serial.h" #endif @@ -59,6 +65,9 @@ #include "norboot-target.h" #endif +#if defined(IPOD_NANO3G) +#define USE_QRCODE +#endif #define ERR_RB 0 #define ERR_OF 1 @@ -144,6 +153,117 @@ static void usb_mode(void) } #endif /* HAVE_BOOTLOADER_USB_MODE */ +#if defined(NAND_CHECK) && defined(HAVE_BOOTLOADER_USB_MODE) \ + && !defined(S5L87XX_DEVELOPMENT_BOOTLOADER) +/* The contributor NAND check (build with -DNAND_CHECK, run from DFU): show + * what the driver identified and how the read-only mount went, add the + * unit's model and firmware version, then serve the raw NAND over USB for + * utils/ipodnano3g/nandcheck/nandcheck.py. Nothing is written to the NAND. */ +/* The bootloader's backlight calls are stubs, and the check's screen has + * come up too dim to read at the default level. Full brightness draws more + * than a battery-less unit's USB power alone can supply and can keep it + * from booting at all, so this stops at the default rather than the max. */ +static void nand_check_light(void) +{ + backlight_hw_brightness(DEFAULT_BRIGHTNESS_SETTING); + backlight_hw_on(); +} + +/* The report's key lines, short enough for the screen; the whole report + * goes to nandcheck.py */ +static void nand_check_print(const char *report) +{ + static const char *const keys[] = { + "banks", "row", "mode", "pagesize", "validated", "ftl", "verdict", + "model", "swvr", + }; + static char text[SECTOR_SIZE]; + char *p, *nl, *sp; + size_t i; + + strlcpy(text, report, sizeof(text)); + for (p = text; (nl = strchr(p, '\n')); p = nl + 1) + { + *nl = '\0'; + sp = strchr(p, ' '); + if (!sp) + continue; + if (!strncmp(p, "ids ", 4)) + { + /* bank 0's id; the others are in the report */ + printf("id %.8s", sp + 1); + continue; + } + for (i = 0; i < ARRAYLEN(keys); i++) + if ((size_t)(sp - p) == strlen(keys[i]) + && !strncmp(p, keys[i], sp - p)) + printf("%s", p); + } +} + +static void nand_check(void) +{ + static struct SysCfg syscfg; + char line[64]; + int rc; + ssize_t n; + + nand_check_light(); + snprintf(line, sizeof(line), "battery %dmV", _battery_voltage()); + rc = storage_init(); + /* USB mode unmounts every volume when the host configures the device, + * through the file system's locks and object lists */ + filesystem_init(); + + lcd_set_foreground(LCD_RBYELLOW); + printf("Nano 3G NAND check"); + lcd_set_foreground(LCD_WHITE); + printf("%s", line); + nand_check_note(line); + nand_check_light(); + if (rc) + printf("storage_init %d", rc); + + /* Which unit this is, but not its serial number */ + n = syscfg_read(&syscfg); + if (n != -1) + { + size_t i, count = MIN(syscfg.header.num_entries, SYSCFG_MAX_ENTRIES); + + for (i = 0; i < count; i++) + { + const struct SysCfgEntry *e = &syscfg.entries[i]; + const uint32_t *w = (const uint32_t *)e->data; + + if (e->tag == SYSCFG_TAG_MODN) + snprintf(line, sizeof(line), "model %.16s", e->data); + else if (e->tag == SYSCFG_TAG_SWVR) + snprintf(line, sizeof(line), "swvr %.16s", e->data); + else if (e->tag == SYSCFG_TAG_HWVR) + snprintf(line, sizeof(line), "hwvr %08lx", + (unsigned long)w[1]); + else + continue; + nand_check_note(line); + } + } + + nand_check_print(nand_check_report()); + lcd_set_foreground(LCD_RBYELLOW); + printf("Photo, then connect USB"); + printf("and run nandcheck.py"); + lcd_set_foreground(LCD_WHITE); + /* One USB session: usb_mode() starts power management each time, so it + * must not run twice */ + usb_mode(); + nand_check_light(); + printf("Done. Hold MENU+SELECT"); + printf("to restart"); + while (1) + sleep(HZ); +} +#endif + void fatal_error(int err) { verbose = true; @@ -770,6 +890,79 @@ static void devel_menu(void) } #endif /* S5L87XX_DEVELOPMENT_BOOTLOADER */ +#ifdef IPOD_NANO3G +static const char qr_code_data_nano[][22] = { + /* + * qrencode -o - -t ascii -s 1 -m 0 -i 'https://rockbox.org/IPNBL' | \ + * cut -c 1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31,33,35,37,39,41 | \ + * awk '{ print "\"" $0 "\"," }' + */ + + "####### # #######", + "# # ## # # #", + "# ### # ## # # ### #", + "# ### # # # # # ### #", + "# ### # # ## # ### #", + "# # #### # #", + "####### # # # #######", + " ## ", + "##### ######## # # # ", + "# # #### # # #", + " # # # #", + "## # # # # ## #### ", + "# ### ## ### ", + " ## # # ####", + "####### ## # ### #", + "# # ### # ## ", + "# ### # # #### ###", + "# ### # ### ### ## ", + "# ### # #### # # ", + "# # # ## ## # #", + "####### # ## ## ", +}; +#define qr_code_data qr_code_data_nano +#endif + +#ifdef USE_QRCODE +static void lcd_qr_code(const char *data, unsigned int row_stride, + int x, int y, unsigned int qr_cols, unsigned int qr_rows, + unsigned int modules_scale, unsigned int margin_thickness) +{ + const unsigned int margin_size = margin_thickness * 2; + const unsigned int width = qr_cols * modules_scale + margin_size; + const unsigned int height = qr_rows * modules_scale + margin_size; + + // -1 = align right, -2 = align center + const unsigned int x0 = x >= 0 ? (unsigned int)x : (LCD_WIDTH - width) / abs(x); + const unsigned int y0 = y >= 0 ? (unsigned int)y : (LCD_HEIGHT - height) / abs(y); + + lcd_set_foreground(LCD_WHITE); + lcd_fillrect(x0, y0, width, height); + + lcd_set_foreground(LCD_BLACK); + for (unsigned int row = 0; row < qr_rows; row++) { + for (unsigned int col = 0; col < qr_cols; col++) { + if (data[row * row_stride + col] != ' ') { + lcd_fillrect( + x0 + margin_thickness + col * modules_scale, + y0 + margin_thickness + row * modules_scale, + modules_scale, + modules_scale + ); + } + } + } + + lcd_update(); +} + +enum { + QR_COLS = sizeof(qr_code_data[0]) - 1, + QR_ROWS = sizeof(qr_code_data) / sizeof(qr_code_data[0]), + QR_STRIDE = sizeof(qr_code_data[0]), +}; +#endif /* USE_QRCODE */ + void main(void) { int rc = 0; @@ -870,6 +1063,11 @@ void main(void) devel_menu(); #endif /* S5L87XX_DEVELOPMENT_BOOTLOADER */ +#if defined(NAND_CHECK) && defined(HAVE_BOOTLOADER_USB_MODE) \ + && !defined(S5L87XX_DEVELOPMENT_BOOTLOADER) + nand_check(); +#endif + #ifndef S5L87XX_DEVELOPMENT_BOOTLOADER if (rc == 0) { #if (CONFIG_STORAGE & STORAGE_ATA) @@ -878,6 +1076,32 @@ void main(void) #endif rc = storage_init(); +#ifdef IPOD_NANO3G + if (rc == NAND_ERR_UNSUPPORTED) { + /* Rockbox only drives NAND chips proven on hardware. Say which + * one this is and how to get it validated, then leave the unit + * to Apple's firmware, which is untouched. */ + lcd_set_foreground(LCD_RBYELLOW); + printf("NAND not supported yet"); + lcd_set_foreground(LCD_WHITE); + printf("chip %08lx x %u", (unsigned long)nand_get_id(), + nand_get_bank_count()); + printf("Rockbox does not write to"); + printf("chips it has not been"); + printf("tested on."); + printf("Starting Apple firmware in 60s..."); + lcd_qr_code(&qr_code_data[0][0], QR_STRIDE, -2, LCD_HEIGHT / 2, QR_COLS, QR_ROWS, 4, 8); + sleep(60 * HZ); /* long enough to read the message and scan the QR code */ + lcd_set_foreground(LCD_WHITE); + rc = kernel_launch_onb(); + /* Only reached if the ONB could not be read from NOR */ + printf("Apple firmware failed: %d", rc); + printf("Hold MENU+SELECT to reboot,"); + printf("then SELECT+PLAY for disk mode"); + while (1) + sleep(HZ); + } +#endif if (rc != 0) { printf("Storage error: %d", rc); fatal_error(ERR_STORAGE); @@ -964,6 +1188,12 @@ void main(void) printf("P%d T%02x S%llx", i, pinfo.type, (unsigned long long)pinfo.size); } +#if defined(IPOD_NANO3G) && defined(DEFAULT_VIRT_SECTOR_SIZE) + /* Nothing mounted: show USB hosts Apple's 4096-byte sectors, which + * its partition table counts in */ + disk_set_sector_multiplier(IF_MD(0,) + DEFAULT_VIRT_SECTOR_SIZE / SECTOR_SIZE); +#endif fatal_error(ERR_RB); } diff --git a/bootloader/iriver_h1x0.c b/bootloader/iriver_h1x0.c index 68c00390a4..8ac0ab4d06 100644 --- a/bootloader/iriver_h1x0.c +++ b/bootloader/iriver_h1x0.c @@ -25,6 +25,7 @@ #include "string.h" #include "cpu.h" #include "system.h" +#include "system-iriver.h" #include "lcd.h" #include "lcd-remote.h" #include "scroll_engine.h" @@ -598,17 +599,3 @@ int usb_screen(void) { return 0; } - -ucschar_t *bidi_l2v(const unsigned char *str, int orientation) -{ - static ucschar_t utf_buf[SCROLL_LINE_SIZE]; - ucschar_t *target; - (void)orientation; - - target = utf_buf; - - while (*str) - str = utf8decode(str, target++); - *target = 0; - return utf_buf; -} diff --git a/bootloader/iriver_h300.c b/bootloader/iriver_h300.c index 28f99597c7..5780802ab8 100644 --- a/bootloader/iriver_h300.c +++ b/bootloader/iriver_h300.c @@ -25,6 +25,7 @@ #include "string.h" #include "cpu.h" #include "system.h" +#include "system-iriver.h" #include "lcd.h" #include "lcd-remote.h" #include "scroll_engine.h" @@ -430,6 +431,9 @@ void main(void) if (rc_on_button) { lcd_remote_init(); + /* ponytail: remote type is set by the tick ISR every 8 ticks; + wait long enough for _remote_type to be valid before reading hold */ + sleep(HZ/8); if (remote_button_hold()) hold_status = true; @@ -591,7 +595,13 @@ void main(void) } /* recheck the hold switch status as it may have changed */ - hold_status = (button_hold() || remote_button_hold()); + /* ponytail: only check remote hold if remote type is known; REMOTETYPE_UNPLUGGED + means lcd_remote_init() hasn't settled yet — GPIO1 bit 20 floats and gives + a false hold=on when a non-LCD remote is connected but the device was booted + from the main ON button. */ + hold_status = button_hold(); + if (remote_type() != REMOTETYPE_UNPLUGGED) + hold_status = hold_status || remote_button_hold(); /* hold switch shutdown or failsafe recovery mode */ if (hold_status || recovery_mode) @@ -658,17 +668,3 @@ int usb_screen(void) { return 0; } - -ucschar_t *bidi_l2v(const unsigned char *str, int orientation) -{ - static ucschar_t utf_buf[SCROLL_LINE_SIZE]; - ucschar_t *target; - (void)orientation; - - target = utf_buf; - - while (*str) - str = utf8decode(str, target++); - *target = 0; - return utf_buf; -} diff --git a/bootloader/rk27xx.c b/bootloader/rk27xx.c index 4f04bebfc8..096555bfbf 100644 --- a/bootloader/rk27xx.c +++ b/bootloader/rk27xx.c @@ -135,9 +135,21 @@ void main(void) if ((btn & ~POWEROFF_BUTTON)) boot = of; - /* if we are woken up by USB insert boot into OF */ +#ifndef FTL_ALLOW_WRITE + /* if we are woken up by USB insert boot into OF: only it can give + * the host write access to the NAND + */ if (DEV_INFO & (1<<20)) boot = of; +#endif + + /* The OF asks for a mode before it resets itself, e.g. at the end of + * a firmware update, and the cell survives the reset. Rockbox never + * sets it, so after a cold start it reads 0: anything else is a + * request from the OF, which expects to come back up. + */ + if (GPIO1_BOOTMODE & 0xff) + boot = of; lcd_clear_display(); diff --git a/bootloader/x1000/boot.c b/bootloader/x1000/boot.c index e196159617..66fd62858a 100644 --- a/bootloader/x1000/boot.c +++ b/bootloader/x1000/boot.c @@ -85,7 +85,7 @@ static int read_linux_args(const char* filename) } /* this isn't 100% correct but will be good enough */ - off_t fsize = filesize(fd); + off_t fsize = ffilesize(fd); if(fsize < 0 || fsize+1 > (off_t)max_size) { splashf(5*HZ, "Arguments too long"); ret = -4; diff --git a/bootloader/x1000/install.c b/bootloader/x1000/install.c index ffdbee709e..9259d9a545 100644 --- a/bootloader/x1000/install.c +++ b/bootloader/x1000/install.c @@ -21,7 +21,7 @@ #include "x1000bootloader.h" #include "kernel.h" #include "button.h" -#include "installer-x1000.h" +#include "installer-ingenic.h" #include enum { diff --git a/bootloader/x1000/main.c b/bootloader/x1000/main.c index 41009253cd..d479e886b0 100644 --- a/bootloader/x1000/main.c +++ b/bootloader/x1000/main.c @@ -28,7 +28,7 @@ #include "disk.h" #include "file_internal.h" #include "usb.h" -#include "i2c-x1000.h" +#include "i2c-ingenic.h" #include "boot-x1000.h" #include diff --git a/bootloader/x1000/utils.c b/bootloader/x1000/utils.c index 2cfde16ff9..c0ec69ac2f 100644 --- a/bootloader/x1000/utils.c +++ b/bootloader/x1000/utils.c @@ -29,7 +29,7 @@ #include "loader_strerror.h" #include "linuxboot.h" #include "screendump.h" -#include "nand-x1000.h" +#include "nand-ingenic.h" #include "sfc-x1000.h" /* Set to true if a SYS_USB_CONNECTED event is seen diff --git a/docs/CREDITS b/docs/CREDITS index d1ac0cfdc7..ada951d769 100644 --- a/docs/CREDITS +++ b/docs/CREDITS @@ -763,6 +763,12 @@ Eivind Ødegård Teun van Dalen Adam N. Burke Lê Khánh Bình +Michael McAllister +Hemant Kumar +Jerry Shen +Olivier Senn +David Cormier +Andrew Rice The libmad team The wavpack team diff --git a/docs/HISTORY b/docs/HISTORY index 3dcc704891..a53bb4c579 100644 --- a/docs/HISTORY +++ b/docs/HISTORY @@ -211,17 +211,17 @@ Rockbox History the LaTeX manual was born - ipod video audio playback + iPod video audio playback * March 2006 iAudio X5 bootloader - ipod Mini 2G audio + iPod Mini 2G audio * April 2006 - ipod Mini 1G audio + iPod Mini 1G audio * July 2006 @@ -231,7 +231,7 @@ Rockbox History Support for Toshiba Gigabeat F - Auto-detect 2048 bytes block-size to work fine on 5.5G ipod video + Auto-detect 2048 bytes block-size to work fine on 5.5G iPod video * January 2007 diff --git a/docs/MAINTAINERS b/docs/MAINTAINERS index da3ec2c371..3c983f0894 100644 --- a/docs/MAINTAINERS +++ b/docs/MAINTAINERS @@ -37,16 +37,16 @@ specific to that target. :Iriver H100/H115: Linus Nielsen Feltzing :Iriver H120/H140: Miika Pekkarinen, Linus Nielsen Feltzing :Iriver H320/H340: Peter D'Hoye -:Ipod 1st Gen: Jens Arnold -:Ipod 2nd Gen: Jens Arnold -:Ipod 3rd Gen: -:Ipod 4th Gen Grayscale: -:Ipod Color/Photo: Dave Chapman -:Ipod Video: -:Ipod Classic: -:Ipod Mini 1st Gen: -:Ipod Mini 2nd Gen: Jens Arnold -:Ipod Nano 1st Gen: Thom Johansen +:iPod 1st Gen: Jens Arnold +:iPod 2nd Gen: Jens Arnold +:iPod 3rd Gen: +:iPod 4th Gen Grayscale: +:iPod Color/Photo: Dave Chapman +:iPod Video: +:iPod Classic: +:iPod Mini 1st Gen: +:iPod Mini 2nd Gen: Jens Arnold +:iPod Nano 1st Gen: Thom Johansen :Iriver H10 20GB: Barry Wardell :Iriver H10 5GB/6GB: :Iaudio X5: Linus Nielsen Feltzing @@ -290,8 +290,8 @@ Bootloaders :HibyOS: Solomon Peachy :Iriver H1xx/H3xx: Miika Pekkarinen, Linus Nielsen Feltzing :Iaudio X5/M5: Linus Nielsen Feltzing -:PortalPlayer (not ipod): Barry Wardell -:Ipod: Dave Chapman +:PortalPlayer (not iPod): Barry Wardell +:iPod: Dave Chapman :Gigabeat F/X: Barry Wardell :xDuoo X3: Solomon Peachy :x1000: Aidan MacDonald diff --git a/docs/PLUGIN_API b/docs/PLUGIN_API index afe701efe2..38a584034a 100644 --- a/docs/PLUGIN_API +++ b/docs/PLUGIN_API @@ -2663,7 +2663,7 @@ char* strtok_r(char *ptr, const char *sep, char **end) int system_memory_guard(int newmode) \group kernel/ system - \conditions ((CONFIG_PLATFORM & PLATFORM_NATIVE)) + \conditions ((CONFIG_PLATFORM & PLATFORM_NATIVE) && defined(CPU_COLDFIRE)) \param newmode \return \description diff --git a/firmware/SOURCES b/firmware/SOURCES index 20158f8489..cd1c3f201a 100644 --- a/firmware/SOURCES +++ b/firmware/SOURCES @@ -25,7 +25,7 @@ target/hosted/rtc.c #if (CONFIG_PLATFORM & PLATFORM_ANDROID) == 0 && \ !defined(DX50) && !defined(DX90) && \ - (defined(DEBUG) || defined(SIMULATOR) || defined(CTRU)) /* sim should define DEBUG instead */ + (defined(DEBUG) || defined(SIMULATOR) || defined(N3DS)) /* sim should define DEBUG instead */ target/hosted/debug-hosted.c #endif @@ -101,14 +101,14 @@ target/hosted/sdl/app/button-application.c #ifdef WIN32 target/hosted/filesystem-win32.c #else /* !WIN32 */ -#ifndef CTRU +#if !(CONFIG_PLATFORM & PLATFORM_GAME_CONSOLE) target/hosted/filesystem-unix.c -#endif /* CTRU */ +#endif /* PLATFORM_GAME_CONSOLE */ #endif /* WIN32 */ #endif /* APPLICATION */ #endif /* HAVE_SDL */ -#if defined(APPLICATION) && !defined(CTRU) +#if defined(APPLICATION) && !(CONFIG_PLATFORM & PLATFORM_GAME_CONSOLE) target/hosted/filesystem-app.c #endif /* APPLICATION */ @@ -187,6 +187,21 @@ target/hosted/hiby/hibylinux_codec.c #endif #endif +#if defined(HIDIZS_AP80MAX) && !defined(SIMULATOR) +target/hosted/hiby/usb-hiby-gadget.c +target/hosted/hiby/ap80max/button-ap80max.c +target/hosted/hiby/ap80max/powermgmt-ap80max.c +target/hosted/hiby/debug-hiby.c +#ifndef BOOTLOADER +target/hosted/hiby/ap80max/led-ap80max.c +target/hosted/hiby/hibylinux_codec.c +#endif +#endif + +#if (defined(HIBY_R1) || defined(HIBY_R3PROII) || defined(HIDIZS_AP80MAX)) && !defined(SIMULATOR) +target/hosted/hiby/usb-dac-hiby.c +#endif + #if defined(AGPTEK_ROCKER) && !defined(SIMULATOR) target/hosted/agptek/button-agptek.c target/hosted/agptek/debug-agptek.c @@ -218,7 +233,7 @@ target/hosted/xduoo/xduoolinux_codec.c target/hosted/button-devinput.c #endif -#if defined(HIBY_LINUX) && !defined(HIBY_R3PROII) && !defined(HIBY_R1) && !defined(SIMULATOR) +#if defined(HIBY_LINUX) && !defined(HIBY_R3PROII) && !defined(HIBY_R1) && !defined(HIDIZS_AP80MAX) && !defined(SIMULATOR) target/hosted/usb-hiby.c #endif @@ -487,6 +502,8 @@ drivers/rtc/rtc_jz4740.c drivers/rtc/rtc_jz4760.c #elif (CONFIG_RTC == RTC_X1000) drivers/rtc/rtc_x1000.c +#elif (CONFIG_RTC == RTC_S35390A) +drivers/rtc/rtc_s35390a.c #elif (CONFIG_RTC == RTC_S35380A) drivers/rtc/rtc_s35380a.c #elif (CONFIG_RTC == RTC_D2) @@ -537,9 +554,9 @@ sound.c pcm_sampr.c pcm.c pcm_mixer.c -#ifdef HAVE_SW_VOLUME_CONTROL +#ifdef WANT_SWVOL pcm_sw_volume.c -#endif /* HAVE_SW_VOLUME_CONTROL */ +#endif /* WANT_SWVOL */ #ifdef HAVE_RECORDING enc_base.c #endif /* HAVE_RECORDING */ @@ -560,7 +577,7 @@ drivers/audio/wm8740.c drivers/audio/wm8751.c #elif defined(HAVE_WM8978) drivers/audio/wm8978.c -#elif defined(HAVE_WM8975) +#elif defined(HAVE_WM8975) || defined(HAVE_WM1870) drivers/audio/wm8975.c #elif defined(HAVE_WM8985) drivers/audio/wm8985.c @@ -609,8 +626,8 @@ target/hosted/sdl/pcm-sdl.c drivers/audio/as3514.c #elif defined(SAMSUNG_YPR1) && defined(HAVE_WM8978) drivers/audio/wm8978.c -#elif defined(CTRU) -drivers/audio/ctru.c +#elif defined(N3DS) +drivers/audio/3ds.c #endif #endif /* (CONFIG_PLATFORM & PLATFORM_NATIVE) */ #endif /* !defined(BOOTLOADER) */ @@ -1620,6 +1637,10 @@ target/arm/s5l8702/ipodnano3g/adc-nano3g.c target/arm/s5l8702/ipodnano3g/powermgmt-nano3g.c target/arm/s5l8702/ipodnano3g/power-nano3g.c target/arm/s5l8702/ipodnano3g/nand-nano3g.c +target/arm/s5l8702/ipodnano3g/ftl-nano3g.c +#ifdef NAND_CHECK +target/arm/s5l8702/ipodnano3g/nand-check-nano3g.c +#endif #if (CONFIG_RTC == RTC_NANO3G) target/arm/s5l8702/ipodnano3g/rtc-nano3g.c // TODO: -> drivers/rtc/rtc_xxx.c ??? #endif @@ -1628,7 +1649,7 @@ target/arm/s5l8702/ipodnano3g/serial-nano3g.c #endif #ifndef BOOTLOADER target/arm/s5l8702/ipodnano3g/audio-nano3g.c -target/arm/s5l8702/ipodnano3g/cscodec-nano3g.c // TODO: -> wmcodec-nano3g.c +target/arm/s5l8702/ipodnano3g/wmcodec-nano3g.c #endif #endif /* IPOD_NANO3G */ @@ -1655,6 +1676,9 @@ target/arm/s5l8702/ipodnano4g/cscodec-nano4g.c #ifdef IPOD_6G target/arm/s5l8702/ipod6g/lcd-6g.c +#if defined(HAVE_COMPOSITE_VIDEO_OUT) && !defined(BOOTLOADER) +target/arm/s5l8702/ipod6g/videoout-6g.c +#endif target/arm/s5l8702/ipod6g/backlight-6g.c target/arm/s5l8702/ipod6g/piezo-6g.c target/arm/s5l8702/ipod6g/pmu-6g.c @@ -1662,6 +1686,10 @@ target/arm/s5l8702/ipod6g/adc-6g.c target/arm/s5l8702/ipod6g/powermgmt-6g.c target/arm/s5l8702/ipod6g/power-6g.c target/arm/s5l8702/ipod6g/storage_ata-6g.c +#if defined(HAVE_HW_H264) && !defined(BOOTLOADER) +target/arm/s5l8702/ipod6g/vpu-6g.c +target/arm/s5l8702/ipod6g/vpu-h264-6g.c +#endif #if (CONFIG_RTC == RTC_NANO2G) target/arm/s5l8702/ipod6g/rtc-6g.c #endif @@ -1671,6 +1699,7 @@ target/arm/s5l8702/ipod6g/serial-6g.c #ifndef BOOTLOADER target/arm/s5l8702/ipod6g/audio-6g.c target/arm/s5l8702/ipod6g/cscodec-6g.c +target/arm/s5l8702/ipod6g/mikey-6g.c #endif /* BOOTLOADER */ #endif /* IPOD_6G */ @@ -1681,8 +1710,23 @@ target/arm/rk27xx/system-rk27xx.c target/arm/rk27xx/backlight-rk27xx.c target/arm/rk27xx/adc-rk27xx.c target/arm/rk27xx/sd-rk27xx.c -target/arm/rk27xx/ftl-rk27xx.c +#if CONFIG_NAND == NAND_RK27XX +#if (CONFIG_STORAGE & STORAGE_NAND) +target/arm/rk27xx/flash-rk27xx.c target/arm/rk27xx/nand-rk27xx.c +target/arm/rk27xx/ftl-rk27xx.c +#if CONFIG_RK27XX_FTL == RK27XX_FTL_SCHEME_A +target/arm/rk27xx/ftl-scheme-a.c +#elif CONFIG_RK27XX_FTL == RK27XX_FTL_SCHEME_B +target/arm/rk27xx/ftl-scheme-b.c +#endif +#elif !defined(BOOTLOADER) +/* no FTL scheme configured: only the finder (debug menu) */ +target/arm/rk27xx/flash-rk27xx.c +target/arm/rk27xx/nand-rk27xx.c +target/arm/rk27xx/ftl-probe-rk27xx.c +#endif +#endif target/arm/rk27xx/usb-rk27xx.c target/arm/rk27xx/lcdif-rk27xx.c target/arm/rk27xx/rkw-loader.c @@ -1702,6 +1746,19 @@ target/arm/rk27xx/rk27generic/lcd-rk27generic.c target/arm/rk27xx/rk27generic/fmradio-i2c-rk27generic.c #endif +#if defined(SAMSUNG_YPCP3) +target/arm/rk27xx/ypcp3/button-ypcp3.c +target/arm/rk27xx/ypcp3/powermgmt-ypcp3.c +target/arm/rk27xx/ypcp3/power-ypcp3.c +target/arm/rk27xx/ypcp3/lcd-ypcp3.c +#ifndef BOOTLOADER +target/arm/rk27xx/ypcp3/wmcodec-ypcp3.c +#endif +#if CONFIG_TUNER +target/arm/rk27xx/rk27generic/fmradio-i2c-rk27generic.c +#endif +#endif + #if CONFIG_CPU == JZ4732 target/mips/ingenic_jz47xx/ata-nand-jz4740.c target/mips/ingenic_jz47xx/ata-sd-jz4740.c @@ -1743,12 +1800,12 @@ target/mips/ingenic_x1000/aic-x1000.c target/mips/ingenic_x1000/boot-x1000.c target/mips/ingenic_x1000/clk-x1000.c target/mips/ingenic_x1000/debug-x1000.c -target/mips/ingenic_x1000/dma-x1000.c -target/mips/ingenic_x1000/gpio-x1000.c -target/mips/ingenic_x1000/i2c-x1000.c -target/mips/ingenic_x1000/kernel-x1000.c +target/mips/ingenic/dma-ingenic.c +target/mips/ingenic/gpio-ingenic.c +target/mips/ingenic/i2c-ingenic.c +target/mips/ingenic/kernel-ingenic.c target/mips/ingenic_x1000/lcd-x1000.c -target/mips/ingenic_x1000/nand-x1000.c +target/mips/ingenic/nand-ingenic.c target/mips/ingenic_x1000/pcm-x1000.c target/mips/ingenic_x1000/pwm-x1000.c target/mips/ingenic_x1000/sfc-x1000.c @@ -1761,11 +1818,8 @@ target/mips/ingenic_x1000/usb-x1000.c #if (CONFIG_STORAGE & (STORAGE_SD|STORAGE_MMC|STORAGE_ATA)) target/mips/ingenic_x1000/msc-x1000.c #endif -#if (CONFIG_STORAGE & STORAGE_SD) -target/mips/ingenic_x1000/sd-x1000.c -#endif #ifdef BOOTLOADER -target/mips/ingenic_x1000/installer-x1000.c +target/mips/ingenic/installer-ingenic.c target/mips/ingenic_x1000/spl-start.S target/mips/ingenic_x1000/spl-x1000.c common/ucl_decompress.c @@ -1805,7 +1859,8 @@ target/mips/ingenic_x1000/fiiom3k/backlight-fiiom3k.c target/mips/ingenic_x1000/fiiom3k/button-fiiom3k.c target/mips/ingenic_x1000/fiiom3k/lcd-fiiom3k.c target/mips/ingenic_x1000/fiiom3k/power-fiiom3k.c -target/mips/ingenic_x1000/spl-nand-x1000.c +target/mips/ingenic_x1000/sdmmc-x1000-common.c +target/mips/ingenic/spl-nand-ingenic.c #endif /* FIIO_M3K */ #if defined(SHANLING_Q1) @@ -1814,7 +1869,8 @@ target/mips/ingenic_x1000/shanlingq1/backlight-shanlingq1.c target/mips/ingenic_x1000/shanlingq1/button-shanlingq1.c target/mips/ingenic_x1000/shanlingq1/lcd-shanlingq1.c target/mips/ingenic_x1000/shanlingq1/power-shanlingq1.c -target/mips/ingenic_x1000/spl-nand-x1000.c +target/mips/ingenic_x1000/sdmmc-x1000-common.c +target/mips/ingenic/spl-nand-ingenic.c #endif /* SHANLING_Q1 */ #if defined(EROS_QN) @@ -1823,7 +1879,8 @@ target/mips/ingenic_x1000/erosqnative/backlight-erosqnative.c target/mips/ingenic_x1000/erosqnative/button-erosqnative.c target/mips/ingenic_x1000/erosqnative/lcd-erosqnative.c target/mips/ingenic_x1000/erosqnative/power-erosqnative.c -target/mips/ingenic_x1000/spl-nand-x1000.c +target/mips/ingenic_x1000/sdmmc-x1000-common.c +target/mips/ingenic/spl-nand-ingenic.c #endif /* EROS_QN */ #if defined(SAMSUNG_YH820) || defined(SAMSUNG_YH920) || defined(SAMSUNG_YH925) @@ -1970,6 +2027,7 @@ target/arm/stm32/echoplayer/pcm-echoplayer.c target/arm/stm32/echoplayer/power-echoplayer.c target/arm/stm32/echoplayer/sdmmc-echoplayer.c target/arm/stm32/echoplayer/system-echoplayer.c +target/arm/stm32/echoplayer/timer-echoplayer.c target/arm/stm32/echoplayer/usb-echoplayer.c #endif @@ -2029,26 +2087,35 @@ target/hosted/tinyalsa/mixer.c target/hosted/tinyalsa/pcm.c #endif -#if (CONFIG_PLATFORM & PLATFORM_CTRU) +#if (CONFIG_PLATFORM & PLATFORM_GAME_CONSOLE) +#ifdef CPU_ARM asm/arm/lcd-as-memframe.S -target/hosted/ctru/backlight-ctru.c -target/hosted/ctru/button-ctru.c -target/hosted/ctru/kernel-ctru.c -target/hosted/ctru/thread-ctru.c -target/hosted/ctru/lcd-bitmap.c -target/hosted/ctru/luminance-ctru.c -target/hosted/ctru/system-ctru.c -target/hosted/ctru/filesystem-ctru.c -target/hosted/ctru/lc-ctru.c -target/hosted/ctru/lc-program-resolver.c -target/hosted/ctru/powermgmt-ctru.c -target/hosted/ctru/timer-ctru.c -target/hosted/ctru/pcm-ctru.c -target/hosted/ctru/lib/sys_file.c -target/hosted/ctru/lib/sys_dir.c -target/hosted/ctru/lib/sys_thread.c -target/hosted/ctru/lib/sys_timer.c -target/hosted/ctru/lib/bfile/bfile.c +#endif +target/hosted/game_console/kernel-console.c +target/hosted/game_console/thread-console.c +target/hosted/game_console/timer-console.c +target/hosted/game_console/lib/sys_atomic.c +target/hosted/game_console/lib/sys_file_common.c +target/hosted/game_console/lib/sys_timer_common.c +target/hosted/game_console/lib/bfile/bfile-rwmutex.c +target/hosted/game_console/lib/sys_cond_generic.c +#ifdef N3DS +target/hosted/game_console/lib/sys_file_3ds.c +target/hosted/game_console/lib/sys_dir_3ds.c +target/hosted/game_console/lib/sys_thread_3ds.c +target/hosted/game_console/lib/sys_timer_3ds.c +target/hosted/game_console/lib/bfile/bfile-3ds.c +target/hosted/game_console/3ds/backlight-3ds.c +target/hosted/game_console/3ds/button-3ds.c +target/hosted/game_console/3ds/filesystem-3ds.c +target/hosted/game_console/3ds/lc-3ds.c +target/hosted/game_console/3ds/lcd-bitmap-3ds.c +target/hosted/game_console/3ds/lc-program-resolver.c +target/hosted/game_console/3ds/luminance-3ds.c +target/hosted/game_console/3ds/pcm-3ds.c +target/hosted/game_console/3ds/powermgmt-3ds.c +target/hosted/game_console/3ds/system-3ds.c +#endif /* N3DS */ #endif #else /* defined(SIMULATOR) */ @@ -2089,6 +2156,7 @@ drivers/cw2015.c #endif #ifdef HAVE_SDMMC_HOST drivers/sdmmc_host.c +drivers/sdmmc_poll.c #endif #endif @@ -2104,7 +2172,7 @@ kernel/semaphore.c #endif #ifdef HAVE_SDL_THREADS target/hosted/sdl/thread-sdl.c -#elif !defined(CTRU) +#elif !(CONFIG_PLATFORM & PLATFORM_GAME_CONSOLE) kernel/thread.c #endif kernel/thread-common.c diff --git a/firmware/asm/arm/pcm-mixer-armv6.c b/firmware/asm/arm/pcm-mixer-armv6.c index 613a9ed25a..450a285689 100644 --- a/firmware/asm/arm/pcm-mixer-armv6.c +++ b/firmware/asm/arm/pcm-mixer-armv6.c @@ -76,25 +76,7 @@ static FORCE_INLINE void write_samples(void *out, { /* Channel is unity amplitude */ #if defined(CPU_ARM_MICRO) && ARCH_VERSION >= 7 - asm volatile ( - "ands r4, %2, #0x1f \n" - "beq 2f \n" - "1: \n" - "ldr r3, [%1], #4 \n" - "subs r4, r4, #4 \n" - "str r3, [%0], #4 \n" - "bne 1b \n" - "bics %2, %2, #0x1f \n" - "beq 3f \n" - "2: \n" - "ldmia %1!, { r3-r10 } \n" - "subs %2, %2, #32 \n" - "stmia %0!, { r3-r10 } \n" - "bhi 2b \n" - "3: \n" - : "+lr"(out), "+lr"(src), "+r"(size) - : - : "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10"); + memcpy(out, src, size); #else asm volatile ( "ands r1, %2, #0x1f \n" diff --git a/firmware/common/dir.c b/firmware/common/dir.c index 1f04c4f46b..2e0878d0ab 100644 --- a/firmware/common/dir.c +++ b/firmware/common/dir.c @@ -104,7 +104,7 @@ static struct dirstr_desc * alloc_dirstr(void) /** POSIX interface **/ /* open a directory */ -DIR * opendir(const char *dirname) +DIR * rbfs_opendir(const char *dirname) { DEBUGF("opendir(dirname=\"%s\"\n", dirname); @@ -132,7 +132,7 @@ file_error: } /* close a directory stream */ -int closedir(DIR *dirp) +int rbfs_closedir(DIR *dirp) { int rc; @@ -159,7 +159,7 @@ file_error: } /* read a directory */ -struct dirent * readdir(DIR *dirp) +struct dirent * rbfs_readdir(DIR *dirp) { struct dirstr_desc * const dir = GET_DIRSTR(READER, dirp); if (!dir) @@ -184,7 +184,7 @@ file_error: #if 0 /* not included now but probably should be */ /* read a directory (reentrant) */ -int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result) +int rbfs_readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result) { if (!result) FILE_ERROR_RETURN(EFAULT, -2); @@ -218,7 +218,7 @@ file_error: } /* reset the position of a directory stream to the beginning of a directory */ -void rewinddir(DIR *dirp) +void rbfs_rewinddir(DIR *dirp) { struct dirstr_desc * const dir = GET_DIRSTR(READER, dirp); if (!dir) @@ -232,7 +232,7 @@ void rewinddir(DIR *dirp) #endif /* 0 */ /* make a directory */ -int mkdir(const char *path) +int rbfs_mkdir(const char *path) { DEBUGF("mkdir(path=\"%s\")\n", path); @@ -269,7 +269,7 @@ file_error: } /* remove a directory */ -int rmdir(const char *name) +int rbfs_rmdir(const char *name) { DEBUGF("rmdir(name=\"%s\")\n", name); @@ -295,7 +295,7 @@ int rmdir(const char *name) /** Extended interface **/ /* return if two directory streams refer to the same directory */ -int samedir(DIR *dirp1, DIR *dirp2) +int rbfs_samedir(DIR *dirp1, DIR *dirp2) { struct dirstr_desc * const dir1 = GET_DIRSTR(WRITER, dirp1); if (!dir1) @@ -312,7 +312,7 @@ int samedir(DIR *dirp1, DIR *dirp2) } /* test directory existence (returns 'false' if a file) */ -bool dir_exists(const char *dirname) +bool rbfs_dir_exists(const char *dirname) { file_internal_lock_WRITER(); bool rc = test_stream_exists_internal(dirname, FF_DIR) > 0; @@ -321,7 +321,7 @@ bool dir_exists(const char *dirname) } /* get the portable info from the native entry */ -struct dirinfo dir_get_info(DIR *dirp, struct dirent *entry) +struct dirinfo rbfs_dir_get_info(DIR *dirp, struct dirent *entry) { int rc; if (!dirp || !entry) @@ -344,7 +344,7 @@ file_error: return (struct dirinfo){ .attribute = 0 }; } -const char* root_realpath(void) +const char* rbfs_root_realpath(void) { /* Native only, for APP and SIM see respective filesystem-.c files */ return root_get_realpath(); /* rb_namespace.c */ diff --git a/firmware/common/dircache.c b/firmware/common/dircache.c index 4b584fca56..6a91b6cb5d 100644 --- a/firmware/common/dircache.c +++ b/firmware/common/dircache.c @@ -1395,15 +1395,12 @@ static void sab_process_dir(struct file_base_info *infop, bool issab) should also filled in beforehand */ /* allocate the stack right now to the max demand */ - struct dirsab - { - struct sab sab; - struct sab_component stack[issab ? DIRCACHE_MAX_DEPTH : 1]; - } dirsab; - struct sab *sabp = &dirsab.sab; + size_t stackdepth = (issab ? DIRCACHE_MAX_DEPTH : 1); + struct sab *sabp = alloca(sizeof(*sabp) + + sizeof(sabp->stack[0]) * stackdepth); sabp->quit = false; - sabp->stackend = &sabp->stack[ARRAYLEN(dirsab.stack)]; + sabp->stackend = &sabp->stack[stackdepth]; sabp->top = sabp->stackend; sabp->info = *infop; @@ -3063,7 +3060,7 @@ int INIT_ATTR dircache_load(void) if (maindata.dircache.size != maindata.dircache.sizeentries + maindata.dircache.sizenames || ALIGN_DOWN(maindata.dircache.size, ENTRYSIZE) != maindata.dircache.size || - filesize(fd) - sizeof (maindata) != maindata.dircache.size) + ffilesize(fd) - sizeof (maindata) != maindata.dircache.size) { logf("dircache: file header error"); goto error_nolock; diff --git a/firmware/common/file.c b/firmware/common/file.c index 12505381fc..1139385c42 100644 --- a/firmware/common/file.c +++ b/firmware/common/file.c @@ -507,7 +507,7 @@ static int open_internal_inner1(const char *path, int oflag, file_error: if (fildes >= 0) - close(fildes); + rbfs_close(fildes); return rc; } @@ -817,21 +817,21 @@ void force_close_writer_internal(struct filestr_base *stream) /** POSIX **/ /* open a file */ -int open(const char *path, int oflag) +int rbfs_open(const char *path, int oflag) { DEBUGF("open(path=\"%s\",oflag=%X)\n", path, (unsigned)oflag); return open_internal_locked(path, oflag, FF_ANYTYPE); } /* create a new file or rewrite an existing one */ -int creat(const char *path) +int rbfs_creat(const char *path) { DEBUGF("creat(path=\"%s\")\n", path); return open_internal_locked(path, O_WRONLY|O_CREAT|O_TRUNC, FF_ANYTYPE); } /* close a file descriptor */ -int close(int fildes) +int rbfs_close(int fildes) { DEBUGF("close(fd=%d)\n", fildes); @@ -857,7 +857,7 @@ file_error: } /* truncate a file to a specified length */ -int ftruncate(int fildes, off_t length) +int rbfs_ftruncate(int fildes, off_t length) { DEBUGF("ftruncate(fd=%d,len=%ld)\n", fildes, (long)length); @@ -889,7 +889,7 @@ file_error: } /* synchronize changes to a file */ -int fsync(int fildes) +int rbfs_fsync(int fildes) { DEBUGF("fsync(fd=%d)\n", fildes); @@ -915,7 +915,7 @@ file_error: } /* move the read/write file offset */ -off_t lseek(int fildes, off_t offset, int whence) +off_t rbfs_lseek(int fildes, off_t offset, int whence) { #ifndef LOGF_ENABLE /* wipes out log before you can save it */ DEBUGF("lseek(fd=%d,ofs=%ld,wh=%d)\n", fildes, (long)offset, whence); @@ -934,7 +934,7 @@ file_error: } /* read from a file */ -ssize_t read(int fildes, void *buf, size_t nbyte) +ssize_t rbfs_read(int fildes, void *buf, size_t nbyte) { struct filestr_desc * const file = GET_FILESTR(READER, fildes); if (!file) @@ -960,7 +960,7 @@ file_error: } /* write on a file */ -ssize_t write(int fildes, const void *buf, size_t nbyte) +ssize_t rbfs_write(int fildes, const void *buf, size_t nbyte) { struct filestr_desc * const file = GET_FILESTR(READER, fildes); if (!file) @@ -986,7 +986,7 @@ file_error: } /* remove a file */ -int remove(const char *path) +int rbfs_remove(const char *path) { DEBUGF("remove(path=\"%s\")\n", path); @@ -997,7 +997,7 @@ int remove(const char *path) } /* rename a file */ -int rename(const char *old, const char *new) +int rbfs_rename(const char *old, const char *new) { DEBUGF("rename(old=\"%s\",new=\"%s\")\n", old, new); @@ -1144,7 +1144,7 @@ file_error: /** Extensions **/ -int modtime(const char *path, time_t modtime) +int rbfs_modtime(const char *path, time_t modtime) { DEBUGF("modtime(path=\"%s\",modtime=%d)\n", path, (int) modtime); @@ -1181,7 +1181,7 @@ file_error: } /* get the binary size of a file (in bytes) */ -off_t filesize(int fildes) +off_t rbfs_ffilesize(int fildes) { struct filestr_desc * const file = GET_FILESTR(READER, fildes); if (!file) @@ -1200,7 +1200,7 @@ file_error: } /* test if two file descriptors refer to the same file */ -int fsamefile(int fildes1, int fildes2) +int rbfs_fsamefile(int fildes1, int fildes2) { struct filestr_desc * const file1 = GET_FILESTR(WRITER, fildes1); if (!file1) @@ -1217,7 +1217,7 @@ int fsamefile(int fildes1, int fildes2) } /* tell the relationship of path1 to path2 */ -int relate(const char *path1, const char *path2) +int rbfs_relate(const char *path1, const char *path2) { /* this is basically what rename() does but reduced to the relationship determination */ @@ -1277,7 +1277,7 @@ file_error: } /* test file or directory existence */ -bool file_exists(const char *path) +bool rbfs_file_exists(const char *path) { file_internal_lock_WRITER(); bool rc = test_stream_exists_internal(path, FF_ANYTYPE) > 0; diff --git a/firmware/common/rb-loader.c b/firmware/common/rb-loader.c index 2f5e06e165..b8d3d5a163 100644 --- a/firmware/common/rb-loader.c +++ b/firmware/common/rb-loader.c @@ -53,7 +53,7 @@ static int load_firmware_filename(unsigned char* buf, if (fd < 0) return EFILE_NOT_FOUND; - len = filesize(fd) - 8; + len = ffilesize(fd) - 8; if (len > buffer_size) { diff --git a/firmware/common/strcasestr.c b/firmware/common/strcasestr.c index f8a2093322..31d3800f18 100644 --- a/firmware/common/strcasestr.c +++ b/firmware/common/strcasestr.c @@ -34,9 +34,11 @@ typedef unsigned chartype; #if defined(PREFER_SIZE_OVER_SPEED) || defined(__OPTIMIZE_SIZE__) -char* strcasestr (const char* haystack, const char* needle) +char* strcasestr (const char* phaystack, const char* pneedle) { - const char* match, *needle_search; + const unsigned char *haystack = (const unsigned char *) phaystack; + const unsigned char *needle = (const unsigned char *) pneedle; + const unsigned char* match, *needle_search; chartype needle_ch, ch_h; chartype first_ch, first_upch; if (*needle == 0) /* if needle empty return haystack */ diff --git a/firmware/common/unicode.c b/firmware/common/unicode.c index 7e5bac8d91..15abc8fde2 100644 --- a/firmware/common/unicode.c +++ b/firmware/common/unicode.c @@ -221,7 +221,7 @@ static int alloc_and_load_cp_table(int cp, void *buf) if (fd < 0) return -1; - off_t size = filesize(fd); + off_t size = ffilesize(fd); if (size > 0 && size <= MAX_CP_TABLE_SIZE*2 && !(size % (off_t)sizeof (uint16_t))) { diff --git a/firmware/common/zip.c b/firmware/common/zip.c index 345eb6cb35..3edeceae82 100644 --- a/firmware/common/zip.c +++ b/firmware/common/zip.c @@ -524,7 +524,7 @@ static off_t zip_file_seek(struct zip* zh, off_t offset, int whence) { static off_t zip_file_size(struct zip* zh) { struct zip_file* z = (struct zip_file*) zh; - return filesize(z->file); + return ffilesize(z->file); } static void zip_file_close(struct zip* zh) { @@ -721,7 +721,7 @@ struct zip* zip_open(const char* name, bool try_mem) { if ((file = open(name, O_RDONLY)) < 0) goto bail; - if (try_mem && (mem_handle = zip_core_alloc(mem_size = filesize(file))) >= 0) { + if (try_mem && (mem_handle = zip_core_alloc(mem_size = ffilesize(file))) >= 0) { if ((zip_handle = zip_core_alloc(sizeof(struct zip_mem))) < 0) goto bail; diff --git a/firmware/core_alloc.c b/firmware/core_alloc.c index 95642e806b..1e2477c1af 100644 --- a/firmware/core_alloc.c +++ b/firmware/core_alloc.c @@ -61,7 +61,7 @@ void core_allocator_init(void) { unsigned char *start = ALIGN_UP(audiobuffer, sizeof(intptr_t)); -#if defined(IPOD_VIDEO) && !defined(BOOTLOADER) && !defined(SIMULATOR) +#if !defined(__PCTOOL__) && defined(IPOD_VIDEO) && !defined(BOOTLOADER) && !defined(SIMULATOR) audiobufend=(unsigned char *)audiobufend_lds; if(MEMORYSIZE==64 && probed_ramsize!=64) { diff --git a/firmware/drivers/audio/ctru.c b/firmware/drivers/audio/3ds.c similarity index 96% rename from firmware/drivers/audio/ctru.c rename to firmware/drivers/audio/3ds.c index 05cca2e76f..b973b1fb87 100644 --- a/firmware/drivers/audio/ctru.c +++ b/firmware/drivers/audio/3ds.c @@ -37,9 +37,6 @@ void audiohw_set_volume(int vol_l, int vol_r) (void)vol_l; (void)vol_r; } -#if defined(AUDIOHW_HAVE_BALANCE) -void audiohw_set_balance(int value) { (void)value; } -#endif #ifndef HAVE_SW_TONE_CONTROLS #if defined(AUDIOHW_HAVE_BASS) void audiohw_set_bass(int value) { (void)value; } diff --git a/firmware/drivers/audio/eros_qn_codec.c b/firmware/drivers/audio/eros_qn_codec.c index fc01f7fd09..2bbab3f3bd 100644 --- a/firmware/drivers/audio/eros_qn_codec.c +++ b/firmware/drivers/audio/eros_qn_codec.c @@ -31,8 +31,8 @@ // #define LOGF_ENABLE #include "logf.h" -#include "gpio-x1000.h" -#include "i2c-x1000.h" +#include "gpio-ingenic.h" +#include "i2c-ingenic.h" int es9018k2m_present_flag = 0; diff --git a/firmware/drivers/audio/rk27xx_codec.c b/firmware/drivers/audio/rk27xx_codec.c index 4357a4c356..3b06d2ba40 100644 --- a/firmware/drivers/audio/rk27xx_codec.c +++ b/firmware/drivers/audio/rk27xx_codec.c @@ -39,8 +39,28 @@ static int codec_read(uint8_t reg, uint8_t *val) } #endif +/* The line input the FM tuner is wired to: RK27XX_CODEC_FM_LINE, 1 or 2, + * from the target config; both when the target does not say. Only the + * tuner's line is bypassed to the output mixer and powered, and only while + * it is being listened to. */ +#if defined(RK27XX_CODEC_FM_LINE) && RK27XX_CODEC_FM_LINE == 1 +#define FM_BYPASS BYPASS1 +#define FM_IN_SB SB_IN1 +#elif defined(RK27XX_CODEC_FM_LINE) && RK27XX_CODEC_FM_LINE == 2 +#define FM_BYPASS BYPASS2 +#define FM_IN_SB SB_IN2 +#else +#define FM_BYPASS (BYPASS1|BYPASS2) +#define FM_IN_SB (SB_IN1|SB_IN2) +#endif + +/* what feeds the output mixer: the DAC, and the tuner's line while the + * radio plays - the DAC stays in the mix, for voice and beeps */ static uint8_t cr1_sel = DACSEL; +/* power: the line inputs in standby unless monitored */ +static uint8_t pmr1 = SB_ADC|SB_MIC|SB_IND|SB_IN1|SB_IN2; + static void audiohw_mute(bool mute) { if (mute) @@ -95,7 +115,7 @@ void audiohw_postinit(void) udelay(1000); /* power up output stage */ - codec_write(PMR1, SB_ADC|SB_MIC|SB_IND); + codec_write(PMR1, pmr1); sleep(HZ/10); GPIO_PDDR |= (1<<7); /* PD7 high */ @@ -152,6 +172,20 @@ void audiohw_set_volume(int vol_l, int vol_r) void audiohw_set_monitor(bool enable) { - cr1_sel = enable ? BYPASS1|BYPASS2 : DACSEL; - codec_write(CR1, cr1_sel|SB_MICBIAS); + if (enable) + { + /* input up before it is mixed in */ + pmr1 &= ~FM_IN_SB; + codec_write(PMR1, pmr1); + cr1_sel = DACSEL|FM_BYPASS; + codec_write(CR1, cr1_sel|SB_MICBIAS); + } + else + { + /* out of the mix before the input goes down */ + cr1_sel = DACSEL; + codec_write(CR1, cr1_sel|SB_MICBIAS); + pmr1 |= FM_IN_SB; + codec_write(PMR1, pmr1); + } } diff --git a/firmware/drivers/audio/sdl.c b/firmware/drivers/audio/sdl.c index 7250f36124..d85fba9933 100644 --- a/firmware/drivers/audio/sdl.c +++ b/firmware/drivers/audio/sdl.c @@ -75,9 +75,6 @@ void audiohw_set_prescaler(int value) } #endif /* AUDIOHW_HAVE_PRESCALER */ -#if defined(AUDIOHW_HAVE_BALANCE) -void audiohw_set_balance(int value) { (void)value; } -#endif #ifndef HAVE_SW_TONE_CONTROLS #if defined(AUDIOHW_HAVE_BASS) void audiohw_set_bass(int value) { (void)value; } diff --git a/firmware/drivers/audio/wm8751.c b/firmware/drivers/audio/wm8751.c index 9a688f6589..006e9d8b78 100644 --- a/firmware/drivers/audio/wm8751.c +++ b/firmware/drivers/audio/wm8751.c @@ -187,6 +187,13 @@ void audiohw_preinit(void) and_l(~(1<<5), &GPIO1_OUT); or_l((1<<5), &GPIO1_ENABLE); or_l((1<<5), &GPIO1_FUNCTION); +#elif defined(SAMSUNG_YPCP3) + /* headphone amplifier enable, GPIO F2 active high: off for now. The + * pin is SDRAM A12 until switched to GPIO, which the 16 MB of SDRAM + * does not need; the original firmware switches it. */ + SCU_IOMUXB_CON |= IOMUX_GPIO_F2; + GPIO_PFDR &= ~(1<<2); + GPIO_PFCON |= (1<<2); #endif /* @@ -233,6 +240,9 @@ void audiohw_postinit(void) /* headphones + line-out */ wmcodec_set_bits(PWRMGMT2, PWRMGMT2_LOUT1 | PWRMGMT2_ROUT1 | PWRMGMT2_LOUT2 | PWRMGMT2_ROUT2); +#elif defined(SAMSUNG_YPCP3) + /* headphones, on OUT2 */ + wmcodec_set_bits(PWRMGMT2, PWRMGMT2_LOUT2 | PWRMGMT2_ROUT2); #else /* headphones */ wmcodec_set_bits(PWRMGMT2, PWRMGMT2_LOUT1 | PWRMGMT2_ROUT1); @@ -286,6 +296,8 @@ void audiohw_postinit(void) or_l((1<<25), &GPIO1_OUT); #elif defined(MPIO_HD300) or_l((1<<5), &GPIO1_OUT); +#elif defined(SAMSUNG_YPCP3) + GPIO_PFDR |= (1<<2); #endif } @@ -293,10 +305,18 @@ void audiohw_set_volume(int vol_l, int vol_r) { vol_l = vol_tenthdb2hw(vol_l); vol_r = vol_tenthdb2hw(vol_r); +#if defined(SAMSUNG_YPCP3) + /* headphones on OUT2 */ + wmcodec_set_masked(LOUT2, LOUT2_LOUT2VOL(vol_l), + LOUT2_LOUT2VOL_MASK); + wmcodec_set_masked(ROUT2, ROUT2_RO2VU | ROUT2_ROUT2VOL(vol_r), + ROUT2_ROUT2VOL_MASK); +#else wmcodec_set_masked(LOUT1, LOUT1_LOUT1VOL(vol_l), LOUT1_LOUT1VOL_MASK); wmcodec_set_masked(ROUT1, ROUT1_RO1VU | ROUT1_ROUT1VOL(vol_r), ROUT1_ROUT1VOL_MASK); +#endif } #ifdef AUDIOHW_HAVE_LINEOUT @@ -353,6 +373,8 @@ void audiohw_close(void) and_l(~(1<<25), &GPIO1_OUT); #elif defined(MPIO_HD300) and_l(~(1<<5), &GPIO1_OUT); +#elif defined(SAMSUNG_YPCP3) + GPIO_PFDR &= ~(1<<2); #endif /* 2. Disable all output buffers. */ @@ -446,6 +468,70 @@ static void audiohw_set_alc(unsigned char level, /* signal level at ADC */ } #endif +#if defined(SAMSUNG_YPCP3) +/* Samsung YP-CP3: the FM tuner on LINPUT1/RINPUT1, the microphone - mono - + * on RINPUT2, the headphones on OUT2. The rk27xx cannot send received + * samples straight back out, so what is heard of an input goes through the + * codec's analog bypass, the input PGA into the output mixers: the radio + * always, the microphone never. As in the original firmware, the radio + * passes the PGA at +12 dB when only listened to, and the microphone gets + * +13 dB of boost and is recorded by the right ADC onto both channels. + * While recording, the gain setting drives the PGA. */ +void audiohw_set_recsrc(int source, bool recording) +{ + audiohw_mute(true); + + /* inputs off */ + wmcodec_clear_bits(LEFTMIX1, LEFTMIX1_LI2LO); + wmcodec_clear_bits(RIGHTMIX2, RIGHTMIX2_RI2RO); + wmcodec_clear_bits(PWRMGMT1, PWRMGMT1_AINL | PWRMGMT1_AINR | + PWRMGMT1_ADCL | PWRMGMT1_ADCR); + wmcodec_set_masked(ADCR, ADCR_RMICBOOST_DISABLED, 0x3 << 4); + wmcodec_set_masked(ADDITIONAL1, ADDITIONAL1_DATSEL_LR, + ADDITIONAL1_DATSEL_MASK); + + switch (source) + { + case AUDIO_SRC_FMRADIO: + wmcodec_set_masked(ADCL, ADCL_LINSEL_LINPUT1, ADCL_LINSEL_MASK); + wmcodec_set_masked(ADCR, ADCR_RINSEL_RINPUT1, ADCR_RINSEL_MASK); + wmcodec_set_bits(PWRMGMT1, PWRMGMT1_AINL | PWRMGMT1_AINR); + if (recording) + { + wmcodec_set_bits(PWRMGMT1, PWRMGMT1_ADCL | PWRMGMT1_ADCR); + } + else + { + wmcodec_set_reg(LINVOL, LINVOL_LIZC | LINVOL_LINVOL(0x27)); + wmcodec_set_reg(RINVOL, RINVOL_RIVU | RINVOL_RIZC | + RINVOL_RINVOL(0x27)); + } + + wmcodec_set_masked(LEFTMIX1, LEFTMIX1_LMIXSEL_ADCLIN | + LEFTMIX1_LI2LOVOL(0x20), + 0x7 | LEFTMIX1_LI2LOVOL_MASK); + wmcodec_set_bits(LEFTMIX1, LEFTMIX1_LI2LO); + wmcodec_set_masked(RIGHTMIX1, RIGHTMIX1_RMIXSEL_ADCRIN, 0x7); + wmcodec_set_masked(RIGHTMIX2, RIGHTMIX2_RI2ROVOL(0x20), + RIGHTMIX2_RI2ROVOL_MASK); + wmcodec_set_bits(RIGHTMIX2, RIGHTMIX2_RI2RO); + break; + + case AUDIO_SRC_MIC: + wmcodec_set_masked(ADCR, ADCR_RINSEL_RINPUT2 | ADCR_RMICBOOST_13DB, + ADCR_RINSEL_MASK | (0x3 << 4)); + wmcodec_set_bits(PWRMGMT1, PWRMGMT1_AINR | PWRMGMT1_ADCR); + wmcodec_set_masked(ADDITIONAL1, ADDITIONAL1_DATSEL_RR, + ADDITIONAL1_DATSEL_MASK); + break; + + default: /* playback */ + break; + } + + audiohw_mute(false); +} +#else /* !SAMSUNG_YPCP3 */ void audiohw_set_recsrc(int source, bool recording) { /* INPUT1 - FM radio @@ -618,6 +704,7 @@ void audiohw_set_recsrc(int source, bool recording) #endif } /* switch(source) */ } +#endif /* SAMSUNG_YPCP3 */ static int digital_gain2hw(int value) { diff --git a/firmware/drivers/audio/wm8975.c b/firmware/drivers/audio/wm8975.c index 46128cc79d..848d972f4c 100644 --- a/firmware/drivers/audio/wm8975.c +++ b/firmware/drivers/audio/wm8975.c @@ -25,6 +25,12 @@ * ****************************************************************************/ +/* + * Wolfson WM8975 codec driver. It also covers the WM1870 fitted to the iPod + * Nano 3G (HAVE_WM1870), which answers the same registers and adds a few + * above them; the differences are behind that define. + */ + #include "logf.h" #include "system.h" #include "kernel.h" @@ -34,6 +40,9 @@ #include "wmcodec.h" #include "audiohw.h" +#ifdef HAVE_WM1870 +#include "pcm_sampr.h" +#endif static unsigned short wm8975_regs[WM8975_NUM_REGISTERS] = { @@ -126,7 +135,35 @@ void audiohw_postinit(void) wm8975_write(PWRMGMT1, wm8975_regs[PWRMGMT1]); audiohw_mute(false); } -#else /* !IPOD_NANO2G */ +#elif defined(HAVE_WM1870) +/* The WM1870, set up as the original firmware does it. It answers the + * WM8975's registers and a few above them. */ + +void audiohw_preinit(void) +{ + wm8975_write(RESET, RESET_RESET); + + wm8975_write(PWRMGMT1, PWRMGMT1_VMIDSEL_50K | PWRMGMT1_VREF); + wm8975_write(AINTFCE, AINTFCE_MS | AINTFCE_LRP_I2S_RLO + | AINTFCE_IWL_16BIT | AINTFCE_FORMAT_I2S); + wm8975_write(PWRMGMT2, PWRMGMT2_DACL | PWRMGMT2_DACR + | PWRMGMT2_LOUT1 | PWRMGMT2_ROUT1); + + wm8975_write(LOUTMIX1, LOUTMIX1_LD2LO | LOUTMIX1_LI2LOVOL(2) + | LOUTMIX1_LMIXSEL_LADCIN); + wm8975_write(ROUTMIX2, ROUTMIX2_RD2RO | ROUTMIX2_RI2ROVOL(2)); + wm8975_write(ROUTMIX1, ROUTMIX1_RMIXSEL_RADCIN); + + wmcodec_write(WM1870_R67, 0x008); + wm8975_write(ADDCTRL2, ADDCTRL2_OUT3SW_MONOOUT | ADDCTRL2_LRCM); + wm8975_write(ADDCTRL3, ADDCTRL3_VROI); +} + +void audiohw_postinit(void) +{ + audiohw_mute(false); +} +#else /* !IPOD_NANO2G && !HAVE_WM1870 */ void audiohw_preinit(void) { /* POWER UP SEQUENCE */ @@ -240,7 +277,15 @@ void audiohw_close(void) /* Note: Disable output before calling this function */ void audiohw_set_frequency(int fsel) { +#ifdef HAVE_WM1870 + /* The rate codes depend on the MCLK, so the target supplies them */ + unsigned short sampctrl = wmcodec_sampctrl(hw_freq_sampr[fsel]); + + if (sampctrl) + wm8975_write(SAMPCTRL, sampctrl); +#else (void)fsel; +#endif } #ifdef HAVE_RECORDING diff --git a/firmware/drivers/axp-2101.c b/firmware/drivers/axp-2101.c index 7cac31eb9c..a0797d72f1 100644 --- a/firmware/drivers/axp-2101.c +++ b/firmware/drivers/axp-2101.c @@ -355,7 +355,7 @@ int axp2101_supply_get_voltage(int supply) int r = i2c_reg_read1(AXP_PMU_BUS, AXP_PMU_ADDR, info->en_reg); if(r < 0) return AXP2101_SUPPLY_DISABLED; - if(r & (1 << info->en_bit) == 0) + if((r & (1 << info->en_bit)) == 0) return AXP2101_SUPPLY_DISABLED; } diff --git a/firmware/drivers/isp1583.c b/firmware/drivers/isp1583.c index 5392b56b6e..eced3dd63d 100644 --- a/firmware/drivers/isp1583.c +++ b/firmware/drivers/isp1583.c @@ -66,6 +66,17 @@ static struct usb_endpoint endpoints[USB_NUM_ENDPOINTS]; static bool high_speed_mode = false; +static bool address_status; +static bool handle_set_address(struct usb_ctrlrequest *req); + +/* SET_ADDRESS status belongs to this driver, not the core EP0 machine. */ +static void complete_transfer(int ep, int dir, int status, int length) +{ + if(ep == EP_CONTROL && address_status) + return; + usb_core_transfer_complete(ep, dir, status, length); +} + static inline void or_int_value(volatile unsigned short *a, volatile unsigned short *b, unsigned long r, unsigned long value) { set_int_value(*a, *b, (r | value)); @@ -342,7 +353,8 @@ static void usb_handle_setup_rx(void) if (len == 8) { ISP1583_DFLOW_CTRLFUN |= DFLOW_CTRLFUN_STATUS; /* Acknowledge packet */ - usb_core_setup_received((struct usb_ctrlrequest*)setup_pkt_buf); + if(!handle_set_address((struct usb_ctrlrequest*)setup_pkt_buf)) + usb_core_setup_received((struct usb_ctrlrequest*)setup_pkt_buf); } else { @@ -529,7 +541,7 @@ static void out_callback(int ep, unsigned char *buf, int len) (void)buf; logf("out_callback(%d, 0x%x, %d)", ep, (int)buf, len); usb_status_ack(ep, DIR_RX); - usb_core_transfer_complete(ep, true, 0, len); /* 0=>status succeeded, haven't worked out status failed yet... */ + complete_transfer(ep, true, 0, len); /* 0=>status succeeded, haven't worked out status failed yet... */ } static void in_callback(int ep, unsigned char *buf, int len) @@ -537,7 +549,7 @@ static void in_callback(int ep, unsigned char *buf, int len) (void)buf; logf("in_callback(%d, 0x%x, %d)", ep, (int)buf, len); usb_status_ack(ep, DIR_TX); - usb_core_transfer_complete(ep, false, 0, len); + complete_transfer(ep, false, 0, len); } int usb_drv_recv_nonblocking(int ep, void* ptr, int length) @@ -679,6 +691,7 @@ void IRAM_ATTR usb_drv_int(void) logf("BRESET"); high_speed_mode = false; bus_reset(); + address_status = false; usb_core_bus_reset(); /* Mask bus reset interrupt */ set_int_value(ISP1583_GEN_INT_A, ISP1583_GEN_INT_B, INT_IEBRST); @@ -736,14 +749,6 @@ void IRAM_ATTR usb_drv_int(void) ZVM_SPECIFIC; } -void usb_drv_set_address(int address) -{ - logf("usb_drv_set_address(0x%x)", address); - ISP1583_INIT_ADDRESS = (address & 0x7F) | INIT_ADDRESS_DEVEN; - - ZVM_SPECIFIC; -} - void usb_drv_set_test_mode(int mode) { logf("usb_drv_set_test_mode(%d)", mode); @@ -819,3 +824,19 @@ const char* dbg_usb_item(int selected_item, void *data, (void)data; } #endif + +static bool handle_set_address(struct usb_ctrlrequest *req) +{ + address_status = false; + if(!usb_drv_is_set_address(req)) + return false; + + const uint8_t address = req->wValue & 0x7f; + address_status = true; + usb_drv_cancel_all_transfers(); + usb_drv_send_nonblocking(EP_CONTROL, NULL, 0); + ISP1583_INIT_ADDRESS = address | INIT_ADDRESS_DEVEN; + ZVM_SPECIFIC; + usb_core_notify_set_address(address); + return true; +} diff --git a/firmware/drivers/lcd-bitmap-common.c b/firmware/drivers/lcd-bitmap-common.c index 68299df246..e1ebd7267b 100644 --- a/firmware/drivers/lcd-bitmap-common.c +++ b/firmware/drivers/lcd-bitmap-common.c @@ -911,6 +911,18 @@ void LCDFN(drawline)(int x1, int y1, int x2, int y2) dinc2 = (deltay - deltax) * 2; xinc1 = 1; yinc1 = 0; + if (x1 > x2) + { + xinc1 = -xinc1; + xinc2 = -xinc2; + } + + if (y1 > y2) + { + d += dinc2; + yinc1 = -yinc1; + yinc2 = -yinc2; + } } else { @@ -920,21 +932,21 @@ void LCDFN(drawline)(int x1, int y1, int x2, int y2) dinc2 = (deltax - deltay) * 2; xinc1 = 0; yinc1 = 1; + if (x1 > x2) + { + d += dinc2; + xinc1 = -xinc1; + xinc2 = -xinc2; + } + + if (y1 > y2) + { + yinc1 = -yinc1; + yinc2 = -yinc2; + } } numpixels++; /* include endpoints */ - if (x1 > x2) - { - xinc1 = -xinc1; - xinc2 = -xinc2; - } - - if (y1 > y2) - { - yinc1 = -yinc1; - yinc2 = -yinc2; - } - x = x1; y = y1; diff --git a/firmware/drivers/m66591.c b/firmware/drivers/m66591.c index 7a4f7da1c8..b891e17e0f 100644 --- a/firmware/drivers/m66591.c +++ b/firmware/drivers/m66591.c @@ -93,6 +93,17 @@ static volatile unsigned short * pipe_ctrl_addr(int pipe) { } } +static bool address_status; +static bool handle_set_address(struct usb_ctrlrequest *req); + +/* SET_ADDRESS status belongs to this driver, not the core EP0 machine. */ +static void complete_transfer(int ep, int dir, int status, int length) +{ + if(ep == EP_CONTROL && address_status) + return; + usb_core_transfer_complete(ep, dir, status, length); +} + static void pipe_init(int pipe) { volatile unsigned short *pipe_cfg; pipe_cfg = pipe_ctrl_addr(pipe); @@ -209,7 +220,8 @@ static void control_received(void) { /* acknowledge packet recieved (clear valid) */ M66591_INTSTAT_MAIN &= ~(1<<3); - usb_core_setup_received(&temp); + if(!handle_set_address(&temp)) + usb_core_setup_received(&temp); } /* This is a helper function, it is used to notife the stack that a transfer is @@ -219,7 +231,7 @@ static void transfer_complete(int endpoint) { M66591_INTCFG_EMP &= ~(1 << endpoint); logf("mxx: ep %d transfer complete", endpoint); int temp=M66591_eps[endpoint].dir ? USB_DIR_IN : USB_DIR_OUT; - usb_core_transfer_complete(endpoint, temp, 0, + complete_transfer(endpoint, temp, 0, M66591_eps[endpoint].count); } @@ -626,9 +638,7 @@ void USB_DEVICE(void) { ******************************************************************************/ /* The M55691 handles this automatically, nothing to do */ -void usb_drv_set_address(int address) { - (void) address; -} + /* This function sets the standard test modes, it is not required, but might as * well implement it since the hardware supports it @@ -935,3 +945,17 @@ void usb_drv_cancel_all_transfers(void) restore_irq(flags); } + +static bool handle_set_address(struct usb_ctrlrequest *req) +{ + address_status = false; + if(!usb_drv_is_set_address(req)) + return false; + + const uint8_t address = req->wValue & 0x7f; + address_status = true; + usb_drv_cancel_all_transfers(); + usb_drv_send_nonblocking(EP_CONTROL, NULL, 0); + usb_core_notify_set_address(address); + return true; +} diff --git a/firmware/drivers/rtc/rtc_s35390a.c b/firmware/drivers/rtc/rtc_s35390a.c new file mode 100644 index 0000000000..5f12f90b87 --- /dev/null +++ b/firmware/drivers/rtc/rtc_s35390a.c @@ -0,0 +1,145 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2009 by Bertrik Sikken + * Copyright (C) 2008 by Robert Kukla + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#include "config.h" +#include "rtc.h" +#if CONFIG_I2C == I2C_S5L8700 +#include "i2c-s5l8700.h" +#elif CONFIG_I2C == I2C_RK27XX +#include "i2c-rk27xx.h" +#endif +#include "timefuncs.h" + +/* Driver for the Seiko S35390A real-time clock chip with i2c interface + + This driver was derived from rtc_mr100.c and adapted for the S35390A + used in the Meizu M3 (and possibly others). + */ + +#define RTC_ADDR 0x60 + +#define STATUS_REG1 0 +#define STATUS_REG2 1 +#define REALTIME_DATA1 2 +#define REALTIME_DATA2 3 +#define INT1_REG 4 +#define INT2_REG 5 +#define CLOCK_CORR_REG 6 +#define FREE_REG 7 + +/* bits, once reversed into the usual order */ +#define STATUS1_24H (1 << 1) /* 1 = 24-hour, 0 = 12-hour (reset) */ +#define HOUR_PM (1 << 6) + +/* The chip keeps the hour as 0-23 or, the default after a power-on reset, + * as 0-11 plus the p.m. flag. The mode is left as found - another firmware + * on the same player may expect it - and the hour converted instead. */ +static bool mode_24h = true; + + +static void reverse_bits(unsigned char* v, int size) +{ + static const unsigned char flipnibble[] = + {0x00, 0x08, 0x04, 0x0C, 0x02, 0x0A, 0x06, 0x0E, + 0x01, 0x09, 0x05, 0x0D, 0x03, 0x0B, 0x07, 0x0F}; + + for (int i = 0; i < size; i++) { + v[i] = (flipnibble[v[i] & 0x0F] << 4) | + flipnibble[(v[i] >> 4) & 0x0F]; + } +} + +void rtc_init(void) +{ + unsigned char status; + + if (i2c_read(RTC_ADDR | (STATUS_REG1 << 1), -1, 1, &status) == 0) + { + reverse_bits(&status, 1); + mode_24h = (status & STATUS1_24H) != 0; + } +} + +int rtc_read_datetime(struct tm *tm) +{ + unsigned char buf[7]; + unsigned int i; + bool pm; + int ret; + + ret = i2c_read(RTC_ADDR | (REALTIME_DATA1 << 1), -1, sizeof(buf), buf); + reverse_bits(buf, sizeof(buf)); + + pm = (buf[4] & HOUR_PM) != 0; + buf[4] &= 0x3f; /* mask out p.m. flag */ + + for (i = 0; i < sizeof(buf); i++) + buf[i] = BCD2DEC(buf[i]); + + tm->tm_sec = buf[6]; + tm->tm_min = buf[5]; + tm->tm_hour = buf[4]; + if (!mode_24h && pm) + { + tm->tm_hour += 12; + } + tm->tm_mday = buf[2]; + tm->tm_mon = buf[1] - 1; + tm->tm_year = buf[0] + 100; + + set_day_of_week(tm); + set_day_of_year(tm); + + return ret; +} + +int rtc_write_datetime(const struct tm *tm) +{ + unsigned char buf[7]; + unsigned int i; + int ret; + + buf[6] = tm->tm_sec; + buf[5] = tm->tm_min; + buf[4] = tm->tm_hour; + buf[3] = tm->tm_wday; + buf[2] = tm->tm_mday; + buf[1] = tm->tm_mon + 1; + buf[0] = tm->tm_year - 100; + + if (!mode_24h) + { + buf[4] = tm->tm_hour % 12; + } + + for (i = 0; i < sizeof(buf); i++) + buf[i] = DEC2BCD(buf[i]); + + if (!mode_24h && tm->tm_hour >= 12) + { + buf[4] |= HOUR_PM; + } + + reverse_bits(buf, sizeof(buf)); + ret = i2c_write(RTC_ADDR | (REALTIME_DATA1 << 1), -1, sizeof(buf), buf); + + return ret; +} + diff --git a/firmware/drivers/sdmmc_host.c b/firmware/drivers/sdmmc_host.c index eae852bcbf..23c75175e3 100644 --- a/firmware/drivers/sdmmc_host.c +++ b/firmware/drivers/sdmmc_host.c @@ -21,6 +21,7 @@ #include "system.h" #include "storage.h" #include "disk_cache.h" +#include "led.h" #include "debug.h" #include "panic.h" #include "logf.h" @@ -48,6 +49,29 @@ DECLARE_FIRST_DRIVE(int sdmmc_mmc_first_drive); static volatile long sdmmc_mmc_last_activity; #endif +static bool sdmmc_host_any_active(void) +{ +#if CONFIG_STORAGE & STORAGE_SD + for (size_t i = 0; i < ARRAYLEN(sdmmc_sd_hosts); ++i) + if (sdmmc_sd_hosts[i]->led_active) + return true; +#endif + +#if CONFIG_STORAGE & STORAGE_MMC + for (size_t i = 0; i < ARRAYLEN(sdmmc_mmc_hosts); ++i) + if (sdmmc_mmc_hosts[i]->led_active) + return true; +#endif + + return false; +} + +static void sdmmc_host_set_led(struct sdmmc_host *host, bool on) +{ + host->led_active = on; + led(sdmmc_host_any_active()); +} + static int sdmmc_host_get_logical_drive(struct sdmmc_host *host) { switch (host->config.type) @@ -144,6 +168,17 @@ void sdmmc_host_set_medium_present(struct sdmmc_host *host, bool present) } #endif +size_t sdmmc_host_get_bus_freq(uint32_t clock) +{ + switch (clock) + { + case SDMMC_BUS_CLOCK_400KHZ: return 400000; + case SDMMC_BUS_CLOCK_25MHZ: return 25000000; + case SDMMC_BUS_CLOCK_50MHZ: return 50000000; + default: return 0; + } +} + #ifdef CONFIG_STORAGE_MULTI static int sdmmc_host_count_drives(struct sdmmc_host **array, size_t array_size) @@ -185,6 +220,8 @@ static void sdmmc_host_bus_reset(struct sdmmc_host *host) host->need_reset = false; host->initialized = false; host->is_hcs_card = false; + host->use_cmd23 = false; + host->quirk_rdwrsingleblock_delay = false; memset(&host->cardinfo, 0, sizeof(host->cardinfo)); } @@ -244,6 +281,33 @@ static bool sdmmc_host_medium_present(struct sdmmc_host *host) #endif } +static void *sdmmc_host_get_dc_buffer(struct sdmmc_host *host) +{ + /* + * The disk cache should have a free buffer before the disk + * is mounted. We use the buffer during card initialization + * and release it before doing I/O so the allocation should + * always succeed. + */ + if (!host->dc_buffer) + { + host->dc_buffer = dc_get_buffer(); + if (!host->dc_buffer) + panicf("%s: OOM", __func__); + } + + return host->dc_buffer; +} + +static void sdmmc_host_release_dc_buffer(struct sdmmc_host *host) +{ + if (host->dc_buffer) + { + dc_release_buffer(host->dc_buffer); + host->dc_buffer = NULL; + } +} + /* * Submit one command to the host controller. */ @@ -261,6 +325,37 @@ static int sdmmc_host_submit_cmd(struct sdmmc_host *host, return host->ops->submit_command(host->controller, cmd, resp); } +/* + * Submit one command and check the card status in its R1 response. + * + * A card reports a rejected command in R1 rather than by failing the + * transfer: it answers normally, the controller sees nothing wrong, and + * the data is silently not committed. Only use this for commands whose + * response is R1 or R1b; R3, R6 and R7 carry unrelated bits in the same + * positions. + * + * Error bits set in ignore_mask are not treated as failures. + */ +static int sdmmc_host_submit_cmd_r1(struct sdmmc_host *host, + const struct sdmmc_host_command *cmd, + uint32_t ignore_mask) +{ + struct sdmmc_host_response resp; + + int rc = sdmmc_host_submit_cmd(host, cmd, &resp); + if (rc) + return rc; + + if (resp.data[0] & SD_R1_CARD_ERROR & ~ignore_mask) + { + logf("%s: cmd%d card status %08lx", __func__, (int)cmd->command, + (unsigned long)resp.data[0]); + return SDMMC_STATUS_ERROR; + } + + return SDMMC_STATUS_OK; +} + /* * Execute an SD APP_CMD */ @@ -428,6 +523,34 @@ static int sdmmc_host_cmd_send_csd(struct sdmmc_host *host) return rc; } +static int sdmmc_host_cmd_send_scr(struct sdmmc_host *host) +{ + struct sdmmc_host_command cmd = { + .command = SD_SEND_SCR, + .buffer = sdmmc_host_get_dc_buffer(host), + .flags = SDMMC_RESP_SHORT | SDMMC_DATA_READ, + .nr_blocks = 1, + .block_len = 8, + }; + + int rc = sdmmc_host_submit_app_cmd(host, &cmd, NULL); + if (rc) + return rc; + + /* + * Card sends the most significant byte first so + * convert the data to native endian. + */ + host->cardinfo.scr[0] = load_be32_aligned(cmd.buffer + 4); + host->cardinfo.scr[1] = load_be32_aligned(cmd.buffer + 0); + + /* Use CMD23 (SET_BLOCK_COUNT) if card reports support */ + if (host->cardinfo.scr[1] & 0x2) + host->use_cmd23 = true; + + return rc; +} + static int sdmmc_host_cmd_select_card(struct sdmmc_host *host) { struct sdmmc_host_command cmd = { @@ -473,6 +596,7 @@ static int sdmmc_host_cmd_switch_freq(struct sdmmc_host *host, { struct sdmmc_host_command cmd = { .command = SD_SWITCH_FUNC, + .buffer = sdmmc_host_get_dc_buffer(host), .flags = SDMMC_RESP_SHORT | SDMMC_DATA_READ, .nr_blocks = 1, .block_len = 64, @@ -485,19 +609,7 @@ static int sdmmc_host_cmd_switch_freq(struct sdmmc_host *host, else return SDMMC_STATUS_ERROR; - /* - * We'll just assume the disk cache will have a free buffer. - * Since the disk isn't mounted, we should have at least one - * free that would otherwise be used by the FAT filesystem. - */ - cmd.buffer = dc_get_buffer(); - if (!cmd.buffer) - panicf("%s: OOM", __func__); - - int rc = sdmmc_host_submit_cmd(host, &cmd, NULL); - - dc_release_buffer(cmd.buffer); - return rc; + return sdmmc_host_submit_cmd(host, &cmd, NULL); } static int sdmmc_host_cmd_set_block_len(struct sdmmc_host *host, int len) @@ -508,7 +620,40 @@ static int sdmmc_host_cmd_set_block_len(struct sdmmc_host *host, int len) .flags = SDMMC_RESP_SHORT, }; - return sdmmc_host_submit_cmd(host, &cmd, NULL); + return sdmmc_host_submit_cmd_r1(host, &cmd, 0); +} + +static int sdmmc_host_cmd_set_block_count(struct sdmmc_host *host, int count) +{ + struct sdmmc_host_response resp; + struct sdmmc_host_command cmd = { + .command = SD_SET_BLOCK_COUNT, + .argument = count, + .flags = SDMMC_RESP_SHORT, + }; + + int rc = sdmmc_host_submit_cmd(host, &cmd, &resp); + if (rc) + return rc; + + if (resp.data[0] & SD_R1_ILLEGAL_COMMAND) + { + logf("sdmmc: card claimed support for CMD23 but rejected it"); + host->use_cmd23 = false; + } + + return rc; +} + +static int sdmmc_host_cmd_send_status(struct sdmmc_host *host) +{ + struct sdmmc_host_command cmd = { + .command = SD_SEND_STATUS, + .argument = host->cardinfo.rca, + .flags = SDMMC_RESP_SHORT, + }; + + return sdmmc_host_submit_cmd_r1(host, &cmd, 0); } static void sdmmc_host_set_controller_bus_width(struct sdmmc_host *host, uint32_t width) @@ -548,56 +693,56 @@ static int sdmmc_host_device_init(struct sdmmc_host *host) if (rc) { logf("sdmmc_host_cmd_go_idle_state: %d", rc); - return rc; + goto out; } rc = sdmmc_host_cmd_send_if_cond(host); if (rc) { logf("sdmmc_host_cmd_send_if_cond: %d", rc); - return rc; + goto out; } rc = sdmmc_host_cmd_send_app_op_cond(host); if (rc) { logf("sdmmc_host_cmd_send_app_op_cond: %d", rc); - return rc; + goto out; } rc = sdmmc_host_cmd_all_send_cid(host); if (rc) { logf("sdmmc_host_cmd_all_send_cid: %d", rc); - return rc; + goto out; } rc = sdmmc_host_cmd_send_rca(host); if (rc) { logf("sdmmc_host_cmd_send_rca: %d", rc); - return rc; + goto out; } rc = sdmmc_host_cmd_send_csd(host); if (rc) { logf("sdmmc_host_cmd_send_csd: %d", rc); - return rc; + goto out; } rc = sdmmc_host_cmd_select_card(host); if (rc) { logf("sdmmc_host_cmd_select_card: %d", rc); - return rc; + goto out; } rc = sdmmc_host_cmd_clr_card_detect(host); if (rc) { logf("sdmmc_host_cmd_select_card: %d", rc); - return rc; + goto out; } /* @@ -610,7 +755,7 @@ static int sdmmc_host_device_init(struct sdmmc_host *host) if (rc) { logf("sdmmc_host_cmd_set_bus_width: %d", rc); - return rc; + goto out; } sdmmc_host_set_controller_bus_width(host, SDMMC_BUS_WIDTH_4BIT); @@ -627,7 +772,7 @@ static int sdmmc_host_device_init(struct sdmmc_host *host) if (rc) { logf("sdmmc_host_cmd_switch_freq: %d", rc); - return rc; + goto out; } sdmmc_host_set_controller_bus_clock(host, SDMMC_BUS_CLOCK_50MHZ); @@ -637,9 +782,26 @@ static int sdmmc_host_device_init(struct sdmmc_host *host) sdmmc_host_set_controller_bus_clock(host, SDMMC_BUS_CLOCK_25MHZ); } - host->initialized = true; - host->cardinfo.initialized = true; - return 0; + rc = sdmmc_host_cmd_send_scr(host); + if (rc) + { + logf("sdmmc_host_cmd_send_scr: %d", rc); + goto out; + } + +out: + if (rc) + { + host->need_reset = true; + } + else + { + host->initialized = true; + host->cardinfo.initialized = true; + } + + sdmmc_host_release_dc_buffer(host); + return rc; } static int sdmmc_host_transfer(struct sdmmc_host *host, @@ -649,6 +811,7 @@ static int sdmmc_host_transfer(struct sdmmc_host *host, int rc = -1; mutex_lock(&host->lock); + sdmmc_host_set_led(host, true); if (!host->enabled) goto out; @@ -666,10 +829,7 @@ static int sdmmc_host_transfer(struct sdmmc_host *host, { rc = sdmmc_host_device_init(host); if (rc) - { - host->need_reset = true; goto out; - } } if (count < 1) @@ -705,6 +865,18 @@ static int sdmmc_host_transfer(struct sdmmc_host *host, cmd.command = SD_WRITE_MULTIPLE_BLOCK; else cmd.command = SD_READ_MULTIPLE_BLOCK; + + /* + * Note: if the card rejects the command the return code + * will be successful, but use_cmd23 is set to false, and + * CMD12 will be used to terminate the read/write instead. + */ + if (host->use_cmd23) + { + rc = sdmmc_host_cmd_set_block_count(host, xfer_count); + if (rc) + goto out; + } } else { @@ -712,6 +884,9 @@ static int sdmmc_host_transfer(struct sdmmc_host *host, cmd.command = SD_WRITE_BLOCK; else cmd.command = SD_READ_SINGLE_BLOCK; + + if (host->quirk_rdwrsingleblock_delay) + udelay(50); } if (host->cardinfo.sd2plus) @@ -719,22 +894,66 @@ static int sdmmc_host_transfer(struct sdmmc_host *host, else cmd.argument = start * SD_BLOCK_SIZE; - rc = sdmmc_host_submit_cmd(host, &cmd, NULL); + rc = sdmmc_host_submit_cmd_r1(host, &cmd, 0); + + if (rc == SDMMC_STATUS_TIMEOUT && + !host->quirk_rdwrsingleblock_delay && + (cmd.command == SD_WRITE_BLOCK || + cmd.command == SD_READ_SINGLE_BLOCK)) + { + logf("sdmmc_host: enabling quirk_rdwrsingleblock_delay"); + + sdmmc_host_bus_reset(host); + rc = sdmmc_host_device_init(host); + if (rc) + goto out; + + host->quirk_rdwrsingleblock_delay = true; + continue; + } + if (rc) goto out; /* - * NOTE: some controllers can send CMD12 automatically after - * the end of a transfer, eg. X1000; it might be worth - * supporting that via a feature flag. + * The R1 above was returned before the data moved, so it cannot + * report anything which went wrong during the transfer. Whatever + * ends the transfer has to be checked too, and a CMD23 transfer + * ends without a command. */ if (xfer_count > 1) { - memset(&cmd, 0, sizeof(cmd)); - cmd.command = SD_STOP_TRANSMISSION; - cmd.flags = SDMMC_RESP_SHORT | SDMMC_RESP_BUSY; + if (host->use_cmd23) + { + rc = sdmmc_host_cmd_send_status(host); + } + else + { + /* + * NOTE: some controllers can send CMD12 automatically + * after the end of a transfer, eg. X1000; it might + * be worth supporting that via a feature flag. + */ + uint32_t ignore = 0; + + /* + * A CMD12-terminated read runs until it is stopped, so + * one ending on the last block of the card will have + * tried to read past the end and set OUT_OF_RANGE. The + * SD spec (4.3.3, "Block Read") requires it to be + * ignored. + */ + if (data_dir == SDMMC_DATA_READ && + start + xfer_count == host->cardinfo.numblocks) + ignore = SD_R1_OUT_OF_RANGE; + + memset(&cmd, 0, sizeof(cmd)); + cmd.command = SD_STOP_TRANSMISSION; + cmd.flags = SDMMC_RESP_SHORT | SDMMC_RESP_BUSY; + + rc = sdmmc_host_submit_cmd_r1(host, &cmd, ignore); + } - rc = sdmmc_host_submit_cmd(host, &cmd, NULL); if (rc) goto out; } @@ -745,6 +964,7 @@ static int sdmmc_host_transfer(struct sdmmc_host *host, } out: + sdmmc_host_set_led(host, false); mutex_unlock(&host->lock); return rc; } diff --git a/firmware/drivers/sdmmc_poll.c b/firmware/drivers/sdmmc_poll.c new file mode 100644 index 0000000000..1e1d9d5baf --- /dev/null +++ b/firmware/drivers/sdmmc_poll.c @@ -0,0 +1,75 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2025-2026 Aidan MacDonald + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#include "sdmmc_poll.h" +#include "sdmmc_host.h" + +/* 300ms poll interval */ +#define SDCARD_POLL_TICKS (300 * HZ / 1000) + +static int sdmmc_poll_timeout(struct timeout *tmo) +{ + struct sdmmc_poll *poll = (void *)tmo->data; + + poll->last_state = poll->curr_state; + poll->curr_state = poll->check_inserted(); + + if (!poll->curr_state && poll->is_inserted) + { + poll->is_inserted = false; + sdmmc_host_set_medium_present(poll->host, false); + } + else if (poll->curr_state && !poll->is_inserted && + poll->curr_state == poll->last_state) + { + poll->is_inserted = true; + sdmmc_host_set_medium_present(poll->host, true); + } + + if (poll->is_polling || (poll->curr_state != poll->last_state)) + return SDCARD_POLL_TICKS; + + return 0; +} + +void sdmmc_poll_init(struct sdmmc_poll *poll, + struct sdmmc_host *host, + bool (*check_inserted) (void)) +{ + poll->host = host; + poll->check_inserted = check_inserted; + + poll->is_inserted = check_inserted(); + poll->curr_state = poll->is_inserted; + poll->last_state = poll->is_inserted; +} + +void sdmmc_poll_start(struct sdmmc_poll *poll) +{ + poll->is_polling = true; + timeout_register(&poll->timeout, sdmmc_poll_timeout, + SDCARD_POLL_TICKS, (intptr_t)poll); +} + +void sdmmc_poll_event(struct sdmmc_poll *poll) +{ + timeout_register(&poll->timeout, sdmmc_poll_timeout, + SDCARD_POLL_TICKS, (intptr_t)poll); + sdmmc_poll_timeout(&poll->timeout); +} diff --git a/firmware/drivers/tuner/si4700.c b/firmware/drivers/tuner/si4700.c index 2767cbbfe8..ce53fdeceb 100644 --- a/firmware/drivers/tuner/si4700.c +++ b/firmware/drivers/tuner/si4700.c @@ -34,7 +34,7 @@ #include "backlight.h" #if defined(SANSA_CLIP) || defined(SANSA_E200V2) || defined(SANSA_FUZE) || defined(SANSA_C200V2) \ - || defined(SANSA_FUZEPLUS) + || defined(SANSA_FUZEPLUS) || defined(SAMSUNG_YPCP3) /* some models use the internal 32 kHz oscillator which needs special attention during initialisation, power-up and power-down. */ #define SI4700_USE_INTERNAL_OSCILLATOR diff --git a/firmware/drivers/usb-designware.c b/firmware/drivers/usb-designware.c index 3a46a367ee..c4aa7262b5 100644 --- a/firmware/drivers/usb-designware.c +++ b/firmware/drivers/usb-designware.c @@ -147,6 +147,17 @@ static uint32_t epmis_msk; static uint32_t ep_periodic_msk; #endif +static bool address_status; +static bool handle_set_address(struct usb_ctrlrequest *req); + +/* SET_ADDRESS status belongs to this driver, not the core EP0 machine. */ +static void complete_transfer(int ep, int dir, int status, int length) +{ + if(ep == EP_CONTROL && address_status) + return; + usb_core_transfer_complete(ep, dir, status, length); +} + static struct usb_dw_ep *usb_dw_get_ep(int epnum, enum usb_dw_epdir epdir) { return &usb_dw_ep_list[epnum][epdir]; @@ -708,6 +719,12 @@ static void usb_dw_epstart(int epnum, enum usb_dw_epdir epdir, #endif #ifdef USB_DW_SHARED_FIFO eptsiz |= MCCNT((ep_periodic_msk >> epnum) & 1); +#else + /* Dedicated FIFO mode still requires a non-zero multi-count for + * periodic IN endpoints. MCCNT(0) transmits no packets. */ + uint32_t eptype = (DWC_DIEPCTL(epnum) >> 18) & 0x3; + if (eptype == EPTYP_ISOCHRONOUS || eptype == EPTYP_INTERRUPT) + eptsiz |= MCCNT(packets); /* rarely, if ever, not 1 */ #endif } @@ -722,7 +739,27 @@ static void usb_dw_epstart(int epnum, enum usb_dw_epdir epdir, DWC_EPDMA(epnum, epdir) = USB_DW_PHYSADDR((uint32_t)buf); #endif DWC_EPTSIZ(epnum, epdir) = eptsiz; - DWC_EPCTL(epnum, epdir) |= EPENA | nak; + if (((DWC_EPCTL(epnum, epdir) >> 18) & 0x3) == EPTYP_ISOCHRONOUS) + { + /* + * Handle frame scheduling for isochronous endpoints. + * + * TODO: this needs to take into account the endpoint's bInterval, + * and currently will not work for USB 2.0 endpoints that require + * one packet per microframe. For slower USB 2.0 endpoints data is + * only transferred on *even* frames; SETD1PIDOF is not used. + * This also breaks for USB 1.0 endpoints with bInterval > 1 for + * the same reason. + */ + if (usb_drv_port_speed() || (usb_drv_get_frame_number() & 1)) + DWC_EPCTL(epnum, epdir) |= EPENA | nak | SETD0PIDEF; + else + DWC_EPCTL(epnum, epdir) |= EPENA | nak | SETD1PIDOF; + } + else + { + DWC_EPCTL(epnum, epdir) |= EPENA | nak; + } #ifdef USB_DW_ARCH_SLAVE /* Enable interrupts to start pushing data into the FIFO */ @@ -768,7 +805,7 @@ static void usb_dw_abort_endpoint(int epnum, enum usb_dw_epdir epdir) if (dw_ep->busy) { usb_dw_flush_endpoint(epnum, epdir); - usb_core_transfer_complete(epnum, (epdir == USB_DW_EPDIR_OUT) ? + complete_transfer(epnum, (epdir == USB_DW_EPDIR_OUT) ? USB_DIR_OUT : USB_DIR_IN, -1, 0); } } @@ -853,7 +890,7 @@ static void usb_dw_handle_xfer_complete(int epnum, enum usb_dw_epdir epdir) semaphore_release(&dw_ep->complete); int total_bytes = dw_ep->req_size - dw_ep->sizeleft; - usb_core_transfer_complete(epnum, (epdir == USB_DW_EPDIR_OUT) ? + complete_transfer(epnum, (epdir == USB_DW_EPDIR_OUT) ? USB_DIR_OUT : USB_DIR_IN, dw_ep->status, total_bytes); } @@ -864,15 +901,15 @@ static void usb_dw_handle_setup_received(void) #endif struct usb_ctrlrequest req; memcpy(&req, ep0_buffer, sizeof(struct usb_ctrlrequest)); + /* Abort both old data/status directions before delivering the new SETUP. + * Flush does not report completions, so notify the core explicitly once. */ usb_dw_flush_endpoint(0, USB_DW_EPDIR_IN); + usb_dw_flush_endpoint(0, USB_DW_EPDIR_OUT); + usb_core_control_cancelled(); usb_dw_ep0_recv(); - if ((req.bRequestType & USB_RECIP_MASK) == USB_RECIP_DEVICE && - (req.bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD && - (req.bRequest == USB_REQ_SET_ADDRESS)) - usb_dw_set_address(req.wValue); - - usb_core_setup_received(&req); + if(!handle_set_address(&req)) + usb_core_setup_received(&req); } #ifdef USB_DW_SHARED_FIFO @@ -971,6 +1008,9 @@ static void usb_dw_oepint(int ep) if (epints & STUP) { usb_dw_handle_setup_received(); + /* SETUP cancelled the old transfer, including any XFRC captured + * in this interrupt snapshot. Do not complete the new request. */ + return; } if (epints & XFRC) @@ -1077,6 +1117,7 @@ static void usb_dw_irq(void) DWC_GINTSTS = USBRST; usb_dw_set_address(0); usb_dw_reset_endpoints(); + address_status = false; usb_core_bus_reset(); } @@ -1336,21 +1377,6 @@ void usb_drv_stall(int endpoint, bool stall, bool in) usb_dw_target_enable_irq(); } -void usb_drv_set_address(int address) -{ -#if 1 - /* Ignored intentionally, because the controller requires us to set the - new address before sending the response for some reason. So we'll - already set it when the control request arrives, before passing that - into the USB core, which will then call this dummy function. */ - (void)address; -#else - usb_dw_target_disable_irq(); - usb_dw_set_address(address); - usb_dw_target_enable_irq(); -#endif -} - int usb_drv_port_speed(void) { return ((DWC_DSTS & 0x6) == 0); @@ -1444,7 +1470,7 @@ void usb_drv_ep_init(const struct usb_drv_ep_alloc_ctx* ctx, int ep) { if(type == EPTYP_ISOCHRONOUS) { - mps = 1023; + mps = usb_drv_port_speed() ? 1024 : 1023; } else { @@ -1507,10 +1533,36 @@ int usb_drv_send(int endpoint, void *ptr, int length) return dw_ep->status; } -int usb_drv_get_frame_number() +int usb_drv_get_frame_number(void) { - // SOFFN is 14 bits, the least significant 3 appear to be some sort of microframe count. - // The USB spec says a frame number is 11 bits. This way we get 1 frame per millisecond, - // just like we're supposed to! - return (DWC_DSTS >> 11) & 0x7FF; + /* + * SOFFN is a 14-bit microframe number for high-speed hosts and + * a plain frame number for full-speed hosts. + */ + if (usb_drv_port_speed()) + return (DWC_DSTS >> 11) & 0x7FF; + else + return (DWC_DSTS >> 8) & 0x3FFF; +} + +static bool handle_set_address(struct usb_ctrlrequest *req) +{ + address_status = false; + if(!usb_drv_is_set_address(req)) + return false; + + const uint8_t address = req->wValue & 0x7f; + address_status = true; + /* Already in the IRQ handler: avoid the public IRQ-mask wrappers. */ + for(int ep = 1; ep < USB_NUM_ENDPOINTS; ep++) + for(int dir = 0; dir < USB_DW_NUM_DIRS; dir++) + if((usb_endpoints & (1 << (ep + USB_DW_DIR_OFF(dir)))) && + usb_dw_get_ep(ep, dir)->active) { + usb_dw_abort_endpoint(ep, dir); + DWC_EPCTL(ep, dir) |= SETD0PIDEF; + } + usb_dw_set_address(address); + usb_dw_transfer(0, USB_DW_EPDIR_IN, NULL, 0); + usb_core_notify_set_address(address); + return true; } diff --git a/firmware/eeprom_settings.c b/firmware/eeprom_settings.c index b2e75ebf54..abd719b89a 100644 --- a/firmware/eeprom_settings.c +++ b/firmware/eeprom_settings.c @@ -23,6 +23,7 @@ #include "crc32.h" #include "system.h" +#include "system-iriver.h" #include "string.h" #include "logf.h" diff --git a/firmware/export/audiohw.h b/firmware/export/audiohw.h index 25e6f46b3d..4b68144abb 100644 --- a/firmware/export/audiohw.h +++ b/firmware/export/audiohw.h @@ -28,19 +28,17 @@ /* define some audiohw caps */ #define TREBLE_CAP (1 << 0) #define BASS_CAP (1 << 1) -#define BALANCE_CAP (1 << 2) -#define CLIPPING_CAP (1 << 3) -#define PRESCALER_CAP (1 << 4) -#define BASS_CUTOFF_CAP (1 << 5) -#define TREBLE_CUTOFF_CAP (1 << 6) -#define EQ_CAP (1 << 7) -#define DEPTH_3D_CAP (1 << 8) -#define LINEOUT_CAP (1 << 9) -#define MONO_VOL_CAP (1 << 10) -#define LIN_GAIN_CAP (1 << 11) -#define MIC_GAIN_CAP (1 << 12) -#define FILTER_ROLL_OFF_CAP (1 << 13) -#define POWER_MODE_CAP (1 << 14) +#define PRESCALER_CAP (1 << 2) +#define BASS_CUTOFF_CAP (1 << 3) +#define TREBLE_CUTOFF_CAP (1 << 4) +#define EQ_CAP (1 << 5) +#define DEPTH_3D_CAP (1 << 6) +#define LINEOUT_CAP (1 << 7) +#define MONO_VOL_CAP (1 << 8) +#define LIN_GAIN_CAP (1 << 9) +#define MIC_GAIN_CAP (1 << 10) +#define FILTER_ROLL_OFF_CAP (1 << 11) +#define POWER_MODE_CAP (1 << 12) /* Used by every driver to export its min/max/default values for its audio settings. */ @@ -171,7 +169,7 @@ struct sound_settings_info #include "wm8751.h" #elif defined(HAVE_WM8978) #include "wm8978.h" -#elif defined(HAVE_WM8975) +#elif defined(HAVE_WM8975) || defined(HAVE_WM1870) #include "wm8975.h" #elif defined(HAVE_WM8985) #include "wm8985.h" @@ -224,7 +222,7 @@ struct sound_settings_info #include "es9218.h" #elif (CONFIG_PLATFORM & PLATFORM_ANDROID) #include "android_codec.h" -#elif ((CONFIG_PLATFORM & (PLATFORM_SDL | PLATFORM_CTRU)) | defined(RG_NANO)) +#elif ((CONFIG_PLATFORM & (PLATFORM_SDL | PLATFORM_GAME_CONSOLE)) | defined(RG_NANO)) #include "sdl_codec.h" #elif defined(DX50) #include "codec-dx50.h" @@ -270,14 +268,6 @@ struct sound_settings_info #define AUDIOHW_HAVE_TREBLE_CUTOFF #endif -#if (AUDIOHW_CAPS & BALANCE_CAP) -#define AUDIOHW_HAVE_BALANCE -#endif - -#if (AUDIOHW_CAPS & CLIPPING_CAP) -#define AUDIOHW_HAVE_CLIPPING -#endif - #if (AUDIOHW_CAPS & PRESCALER_CAP) #define AUDIOHW_HAVE_PRESCALER #endif @@ -460,7 +450,7 @@ void audiohw_close(void); * Set new volume value * @param val to set in centibels. * NOTE: AUDIOHW_CAPS need to contain - * CLIPPING_CAP + * MONO_VOL_CAP */ void audiohw_set_volume(int val); #else /* Stereo volume */ @@ -481,7 +471,6 @@ void audiohw_set_volume(int vol_l, int vol_r); void audiohw_set_lineout_volume(int vol_l, int vol_r); #endif -#ifndef AUDIOHW_HAVE_CLIPPING #if defined(AUDIOHW_HAVE_BASS) || defined(AUDIOHW_HAVE_TREBLE) \ || defined(AUDIOHW_HAVE_EQ) /** @@ -492,17 +481,6 @@ void audiohw_set_lineout_volume(int vol_l, int vol_r); */ void audiohw_set_prescaler(int val); #endif -#endif /* !AUDIOHW_HAVE_CLIPPING */ - -#ifdef AUDIOHW_HAVE_BALANCE -/** - * Set new balance value - * @param val to set. - * NOTE: AUDIOHW_CAPS need to contain - * BALANCE_CAP - */ -void audiohw_set_balance(int val); -#endif #ifdef AUDIOHW_HAVE_TREBLE /** @@ -718,4 +696,12 @@ AUDIOHW_SETTING(STEREO_WIDTH, "%", 0, 5, 0, 250, 100) # define PCM_NATIVE_BITDEPTH 16 #endif +#ifndef PCM_NATIVE_VOLUME_TYPE +#if !defined(HAVE_SW_VOLUME_CONTROL) || defined(PCM_SW_VOLUME_UNBUFFERED) +#define PCM_NATIVE_VOLUME_TYPE PCM_SINK_HWVOL +#else +#define PCM_NATIVE_VOLUME_TYPE PCM_SINK_SWVOL +#endif +#endif + #endif /* _AUDIOHW_H_ */ diff --git a/firmware/export/button.h b/firmware/export/button.h index dadbf4e5a2..479fd95679 100644 --- a/firmware/export/button.h +++ b/firmware/export/button.h @@ -106,13 +106,17 @@ int button_apply_acceleration(const unsigned int data); #define BUTTON_MULTIMEDIA_NEXT (BUTTON_MULTIMEDIA|0x08) #define BUTTON_MULTIMEDIA_REW (BUTTON_MULTIMEDIA|0x10) #define BUTTON_MULTIMEDIA_FFWD (BUTTON_MULTIMEDIA|0x20) +#define BUTTON_MULTIMEDIA_VOLUME_UP (BUTTON_MULTIMEDIA|0x40) +#define BUTTON_MULTIMEDIA_VOLUME_DOWN (BUTTON_MULTIMEDIA|0x80) #define BUTTON_MULTIMEDIA_ALL (BUTTON_MULTIMEDIA_PLAYPAUSE| \ BUTTON_MULTIMEDIA_STOP| \ BUTTON_MULTIMEDIA_PREV| \ BUTTON_MULTIMEDIA_NEXT| \ BUTTON_MULTIMEDIA_REW | \ - BUTTON_MULTIMEDIA_FFWD) + BUTTON_MULTIMEDIA_FFWD| \ + BUTTON_MULTIMEDIA_VOLUME_UP| \ + BUTTON_MULTIMEDIA_VOLUME_DOWN) #ifdef HAVE_TOUCHSCREEN long touchscreen_last_touch(void); diff --git a/firmware/export/config.h b/firmware/export/config.h index 997d14e010..7a206afb32 100644 --- a/firmware/export/config.h +++ b/firmware/export/config.h @@ -88,11 +88,11 @@ * bit fields to allow PLATFORM_HOSTED to be OR'ed e.g. with a * possible future PLATFORM_ANDROID (some OSes might need totally different * handling to run on them than a stand-alone application) */ -#define PLATFORM_NATIVE (1<<0) -#define PLATFORM_HOSTED (1<<1) -#define PLATFORM_ANDROID (1<<2) -#define PLATFORM_SDL (1<<3) -#define PLATFORM_CTRU (1<<4) +#define PLATFORM_NATIVE (1<<0) +#define PLATFORM_HOSTED (1<<1) +#define PLATFORM_ANDROID (1<<2) +#define PLATFORM_SDL (1<<3) +#define PLATFORM_GAME_CONSOLE (1<<4) /* CONFIG_KEYPAD */ #define IRIVER_H100_PAD 4 @@ -155,8 +155,9 @@ #define ECHO_R1_PAD 75 #define SURFANS_F28_PAD 76 #define RG_NANO_PAD 77 -#define CTRU_PAD 78 +#define N3DS_PAD 78 #define HIBY_R3PROII_PAD 79 +#define HIDIZS_AP80MAX_PAD 80 /* CONFIG_REMOTE_KEYPAD */ #define H100_REMOTE 1 @@ -324,6 +325,11 @@ Lyre prototype 1 */ #define NAND_RK27XX 5 #define NAND_IMX233 6 +/* CONFIG_RK27XX_FTL: the on-flash format of an rk27xx target's NAND. A + * target that does not define it builds only the FTL scheme finder. */ +#define RK27XX_FTL_SCHEME_A 1 /* ftl-scheme-a.c: zone tables, remap logs */ +#define RK27XX_FTL_SCHEME_B 2 /* ftl-scheme-b.c: self-describing blocks */ + /* CONFIG_RTC */ #define RTC_HOSTED 1 /* Generic hosted */ #define RTC_PCF50605 2 /* iPod 3G, 4G & Mini */ @@ -338,6 +344,7 @@ Lyre prototype 1 */ #define RTC_MR100 12 #define RTC_MC13783 13 /* Freescale MC13783 PMIC */ #define RTC_S5L8700 14 +#define RTC_S35390A 15 #define RTC_JZ4740 16 /* Ingenic Jz4740 */ #define RTC_NANO2G 17 /* This seems to be a PCF5063x */ #define RTC_D2 18 /* Either PCF50606 or PCF50635 */ @@ -482,6 +489,8 @@ Lyre prototype 1 */ #include "config/mpiohd300.h" #elif defined(RK27_GENERIC) #include "config/rk27generic.h" +#elif defined(SAMSUNG_YPCP3) +#include "config/samsungypcp3.h" #elif defined(HM60X) #include "config/hifimanhm60x.h" #elif defined(HM801) @@ -574,12 +583,14 @@ Lyre prototype 1 */ #include "config/surfansf28.h" #elif defined(RG_NANO) #include "config/rgnano.h" -#elif defined(CTRU) -#include "config/ctru.h" +#elif defined(N3DS) +#include "config/3ds.h" #elif defined(HIBY_R3PROII) #include "config/hibyr3proii.h" #elif defined(HIBY_R1) #include "config/hibyr1.h" +#elif defined(HIDIZS_AP80MAX) +#include "config/hidizsap80max.h" #else #error "unknown hardware platform!" #endif @@ -887,6 +898,17 @@ Lyre prototype 1 */ /* Storage related config handling */ +/* The rk27xx NAND's Scheme A flash translation layer holds part-written + * pages in RAM (ftl-scheme-a.c) until a later write completes them; + * storage_flush() commits them at shutdown, ROLO and wherever else it is + * called. */ +#if (CONFIG_STORAGE & STORAGE_NAND) && defined(CONFIG_NAND) \ + && (CONFIG_NAND == NAND_RK27XX) \ + && (CONFIG_RK27XX_FTL == RK27XX_FTL_SCHEME_A) \ + && !defined(HAVE_STORAGE_FLUSH) +#define HAVE_STORAGE_FLUSH +#endif + #if (CONFIG_STORAGE & (CONFIG_STORAGE - 1)) != 0 /* Multiple storage drivers */ #define CONFIG_STORAGE_MULTI @@ -1012,7 +1034,7 @@ Lyre prototype 1 */ #if defined(ASSEMBLER_THREADS) \ || defined(HAVE_WIN32_FIBER_THREADS) \ || defined(HAVE_SIGALTSTACK_THREADS) \ - || defined(CTRU) + || defined(N3DS) #define HAVE_PRIORITY_SCHEDULING #endif @@ -1491,4 +1513,12 @@ Lyre prototype 1 */ #define ucschar_t unsigned short #endif +#ifdef HAVE_SW_VOLUME_CONTROL +#define WANT_SWVOL +#endif + +#if defined(PCM_NATIVE_BITDEPTH) && (PCM_NATIVE_BITDEPTH > 16) +#define WANT_SWVOL_32 +#endif + #endif /* __CONFIG_H__ */ diff --git a/firmware/export/config/ctru.h b/firmware/export/config/3ds.h similarity index 93% rename from firmware/export/config/ctru.h rename to firmware/export/config/3ds.h index da9c2b3fb3..302c671935 100644 --- a/firmware/export/config/ctru.h +++ b/firmware/export/config/3ds.h @@ -3,12 +3,12 @@ */ /* We don't run on hardware directly */ -#define CONFIG_PLATFORM (PLATFORM_HOSTED|PLATFORM_CTRU) +#define CONFIG_PLATFORM (PLATFORM_HOSTED|PLATFORM_GAME_CONSOLE) #define HAVE_FPU /* For Rolo and boot loader */ #define MODEL_NUMBER 100s -#define MODEL_NAME "CTRU" +#define MODEL_NAME "N3DS" #define USB_NONE @@ -73,10 +73,11 @@ /* #define HAVE_SCROLLWHEEL */ -#define CONFIG_KEYPAD CTRU_PAD +#define CONFIG_KEYPAD N3DS_PAD -#define HAVE_CTRU_AUDIO +#define HAVE_3DS_AUDIO #define HAVE_HEADPHONE_DETECTION +#define HAVE_SW_TONE_CONTROLS /* #define HAVE_SW_VOLUME_CONTROL */ /* #define PCM_SW_VOLUME_UNBUFFERED */ /* #define PCM_SW_VOLUME_FRACBITS (16) */ diff --git a/firmware/export/config/echor1.h b/firmware/export/config/echor1.h index dd8ad30f66..f6c8760db0 100644 --- a/firmware/export/config/echor1.h +++ b/firmware/export/config/echor1.h @@ -15,7 +15,7 @@ #define CPU_FREQ 480000000 #ifndef SIMULATOR -#define TIMER_FREQ STM32_HSE_FREQ +#define TIMER_FREQ (CPU_FREQ / 2) #endif /* Kernel defines */ diff --git a/firmware/export/config/erosqnative.h b/firmware/export/config/erosqnative.h index cfba1b8ff2..e90670fac2 100644 --- a/firmware/export/config/erosqnative.h +++ b/firmware/export/config/erosqnative.h @@ -95,6 +95,9 @@ #define STORAGE_WANTS_ALIGN #define STORAGE_NEEDS_BOUNCE_BUFFER +/* One SD card slot */ +#define SDMMC_HOST_NUM_SD_CONTROLLERS 1 + /* Power management */ #define CONFIG_BATTERY_MEASURE (VOLTAGE_MEASURE | PERCENTAGE_MEASURE/*|CURRENT_MEASURE*/) #define CONFIG_CHARGING CHARGING_MONITOR diff --git a/firmware/export/config/fiiom3k.h b/firmware/export/config/fiiom3k.h index baf633be70..1093d91a31 100644 --- a/firmware/export/config/fiiom3k.h +++ b/firmware/export/config/fiiom3k.h @@ -84,6 +84,9 @@ #define STORAGE_WANTS_ALIGN #define STORAGE_NEEDS_BOUNCE_BUFFER +/* One SD card slot */ +#define SDMMC_HOST_NUM_SD_CONTROLLERS 1 + /* RTC settings */ #define CONFIG_RTC RTC_X1000 /* TODO: implement HAVE_RTC_ALARM */ diff --git a/firmware/export/config/hibylinux.h b/firmware/export/config/hibylinux.h index 98e8893a41..1b2e2deee4 100644 --- a/firmware/export/config/hibylinux.h +++ b/firmware/export/config/hibylinux.h @@ -39,7 +39,11 @@ #define CODEC_SIZE 0x100000 /* The number of bytes reserved for loadable plugins */ -#define PLUGIN_BUFFER_SIZE 0x80000 +#if MEMORYSIZE >= 16 +#define PLUGIN_BUFFER_SIZE 0x200000 /* 2 MiB */ +#else +#define PLUGIN_BUFFER_SIZE 0x80000 /* 512 KiB */ +#endif #define CONFIG_LCD LCD_INGENIC_LINUX diff --git a/firmware/export/config/hibyr1.h b/firmware/export/config/hibyr1.h index f830791d9e..6a24de6e2e 100644 --- a/firmware/export/config/hibyr1.h +++ b/firmware/export/config/hibyr1.h @@ -37,6 +37,8 @@ #ifndef BOOTLOADER #define HAVE_BUTTON_DATA #define HAVE_TOUCHSCREEN +/* Finger-sized keys, tapped directly, instead of a navigated cursor */ +#define HAVE_KBD_POINT_MODE #endif #ifndef CONFIG_BACKLIGHT_FADING @@ -84,6 +86,7 @@ /* USB */ #define HAVE_USB_ADB +#define HAVE_HOST_USB_AUDIO #define HAVE_USB_POWER #define USB_VID_STR "32BB" #define USB_PID_STR "0101" diff --git a/firmware/export/config/hibyr3proii.h b/firmware/export/config/hibyr3proii.h index ffb0c69f16..b4e2baa6dd 100644 --- a/firmware/export/config/hibyr3proii.h +++ b/firmware/export/config/hibyr3proii.h @@ -37,6 +37,8 @@ #ifndef BOOTLOADER #define HAVE_BUTTON_DATA #define HAVE_TOUCHSCREEN +/* Finger-sized keys, tapped directly, instead of a navigated cursor */ +#define HAVE_KBD_POINT_MODE #endif #ifndef CONFIG_BACKLIGHT_FADING @@ -87,6 +89,7 @@ /* USB */ #define HAVE_USB_ADB +#define HAVE_HOST_USB_AUDIO #define HAVE_USB_POWER #define USB_VID_STR "32BB" #define USB_PID_STR "0101" diff --git a/firmware/export/config/hidizsap80max.h b/firmware/export/config/hidizsap80max.h new file mode 100644 index 0000000000..38d8b49d31 --- /dev/null +++ b/firmware/export/config/hidizsap80max.h @@ -0,0 +1,105 @@ +/* + * This config file is for the Hidizs AP80 Pro Max based on the x1600E soc + */ + +/* For Rolo and boot loader */ +#define MODEL_NUMBER 125 +#define MODEL_NAME "HIDIZS AP80 PRO MAX" + +#define PIVOT_ROOT "/data/mnt/sd_0" +#define MULTIDRIVE_DIR "/data/mnt/usb" + +/* LCD dimensions. The framebuffer is not the panel: /dev/fb0 is 480x640 but + only the center 360 columns are scanned out - see hiby/lcd-target.h. + sqrt(360^2 + 640^2) / 2.95" = 249 */ +#define LCD_WIDTH 360 +#define LCD_HEIGHT 640 +#define LCD_DPI 249 + +#define HAVE_LCD_SLEEP +#define LCD_SLEEP_TIMEOUT (2*HZ) + +#define LCD_DEPTH 16 /* 65536 colours */ +#define LCD_PIXELFORMAT RGB565 /* rgb565 */ + +/* /dev/fb0 is 480 columns wide but only the center 360 display */ +#define FB_STRIDE_MISMATCH 480 +#define FB_STRIDE_XOFFSET ((FB_STRIDE_MISMATCH - LCD_WIDTH) / 2) + +#define CPU_FREQ 1008000000 + +#ifndef SIMULATOR +#define HAVE_GENERAL_PURPOSE_LED +#endif + +/* define this if you have access to the quickscreen */ +#define HAVE_QUICKSCREEN +#define HAVE_HOTKEY + +#define HAVE_HEADPHONE_DETECTION + +#ifndef BOOTLOADER +#define HAVE_BUTTON_DATA +#define HAVE_TOUCHSCREEN +#endif + +/* Identity, as the digitiser already reports in panel coordinates. EVIOCGABS + declares twice that range but the hardware does not use it */ +#define DEFAULT_TOUCHSCREEN_CALIBRATION { .A=1, .B=0, .C=0, \ + .D=0, .E=1, .F=0, \ + .divider=1 } + +#define HAVE_SCROLLWHEEL + +#ifndef CONFIG_BACKLIGHT_FADING +#undef CONFIG_BACKLIGHT_FADING +#endif + +/* KeyPad configuration for plugins */ +#define CONFIG_KEYPAD HIDIZS_AP80MAX_PAD + +/* There are no volume buttons, so HAVE_VOLUME_IN_LIST is not defined - the + scroll wheel handles volume instead */ + +/* Battery */ +#define BATTERY_TYPES_COUNT 1 + +/* Audio codec */ +#define HAVE_HIBY_LINUX_CODEC + +/* We don't have hardware controls */ +#define HAVE_SW_TONE_CONTROLS + +/* HW codec is flexible */ +#define HW_SAMPR_CAPS SAMPR_CAP_ALL_192 + +/* Battery */ +#define CONFIG_BATTERY_MEASURE (VOLTAGE_MEASURE|PERCENTAGE_MEASURE|TIME_MEASURE) + +#define BATTERY_CAPACITY_DEFAULT 100 /* default battery capacity */ +#define BATTERY_CAPACITY_MIN 100 /* min. capacity selectable */ +#define BATTERY_CAPACITY_MAX 100 /* max. capacity selectable */ +#define BATTERY_CAPACITY_INC 0 /* capacity increment */ + +/* Special backlight paths */ +#define BACKLIGHT_HIBY + +#define MIN_BRIGHTNESS_SETTING 1 +#define MAX_BRIGHTNESS_SETTING 100 +#define BRIGHTNESS_STEP 5 +#define DEFAULT_BRIGHTNESS_SETTING 100 + +/* ROLO */ +#define BOOTFILE_EXT "ap80max" +#define BOOTFILE "rockbox." BOOTFILE_EXT +#define BOOTDIR "/.rockbox" + +/* USB */ +#define HAVE_USB_ADB +#define HAVE_HOST_USB_AUDIO +#define HAVE_USB_POWER +#define USB_VID_STR "C502" +#define USB_PID_STR "008C" + +/* Generic HiBy stuff */ +#include "hibylinux.h" diff --git a/firmware/export/config/hifietma8.h b/firmware/export/config/hifietma8.h index 80f6be3b9e..b40c22a4c0 100644 --- a/firmware/export/config/hifietma8.h +++ b/firmware/export/config/hifietma8.h @@ -45,7 +45,11 @@ /* define this if you have a flash memory storage */ #define HAVE_FLASH_STORAGE -#define CONFIG_STORAGE (STORAGE_SD | STORAGE_NAND) +/* The NAND's FTL scheme is not known yet, so the NAND is not storage here: + * only the FTL scheme finder is built (Debug > View FTL scheme). Once a + * report confirms the scheme, define CONFIG_RK27XX_FTL and add STORAGE_NAND, + * as config/hifimanhm60x.h does. */ +#define CONFIG_STORAGE STORAGE_SD #define CONFIG_NAND NAND_RK27XX #define HAVE_SW_TONE_CONTROLS @@ -53,7 +57,7 @@ /* commented for now */ /* #define HAVE_HOTSWAP */ -#define NUM_DRIVES 2 +#define NUM_DRIVES 1 #define SECTOR_SIZE 512 /* for small(ish) SD cards */ diff --git a/firmware/export/config/hifietma8c.h b/firmware/export/config/hifietma8c.h index 8504fa1e55..1006d8721b 100644 --- a/firmware/export/config/hifietma8c.h +++ b/firmware/export/config/hifietma8c.h @@ -45,7 +45,11 @@ /* define this if you have a flash memory storage */ #define HAVE_FLASH_STORAGE -#define CONFIG_STORAGE (STORAGE_SD | STORAGE_NAND) +/* The NAND's FTL scheme is not known yet, so the NAND is not storage here: + * only the FTL scheme finder is built (Debug > View FTL scheme). Once a + * report confirms the scheme, define CONFIG_RK27XX_FTL and add STORAGE_NAND, + * as config/hifimanhm60x.h does. */ +#define CONFIG_STORAGE STORAGE_SD #define CONFIG_NAND NAND_RK27XX #define HAVE_SW_TONE_CONTROLS @@ -53,7 +57,7 @@ /* commented for now */ /* #define HAVE_HOTSWAP */ -#define NUM_DRIVES 2 +#define NUM_DRIVES 1 #define SECTOR_SIZE 512 /* for small(ish) SD cards */ diff --git a/firmware/export/config/hifietma9.h b/firmware/export/config/hifietma9.h index 4c9eea2594..123a549f99 100644 --- a/firmware/export/config/hifietma9.h +++ b/firmware/export/config/hifietma9.h @@ -45,7 +45,11 @@ /* define this if you have a flash memory storage */ #define HAVE_FLASH_STORAGE -#define CONFIG_STORAGE (STORAGE_SD | STORAGE_NAND) +/* The NAND's FTL scheme is not known yet, so the NAND is not storage here: + * only the FTL scheme finder is built (Debug > View FTL scheme). Once a + * report confirms the scheme, define CONFIG_RK27XX_FTL and add STORAGE_NAND, + * as config/hifimanhm60x.h does. */ +#define CONFIG_STORAGE STORAGE_SD #define CONFIG_NAND NAND_RK27XX #define HAVE_SW_TONE_CONTROLS @@ -53,7 +57,7 @@ /* commented for now */ /* #define HAVE_HOTSWAP */ -#define NUM_DRIVES 2 +#define NUM_DRIVES 1 #define SECTOR_SIZE 512 /* for small(ish) SD cards */ diff --git a/firmware/export/config/hifietma9c.h b/firmware/export/config/hifietma9c.h index bcc4505d54..b40d9bd4e9 100644 --- a/firmware/export/config/hifietma9c.h +++ b/firmware/export/config/hifietma9c.h @@ -45,7 +45,11 @@ /* define this if you have a flash memory storage */ #define HAVE_FLASH_STORAGE -#define CONFIG_STORAGE (STORAGE_SD | STORAGE_NAND) +/* The NAND's FTL scheme is not known yet, so the NAND is not storage here: + * only the FTL scheme finder is built (Debug > View FTL scheme). Once a + * report confirms the scheme, define CONFIG_RK27XX_FTL and add STORAGE_NAND, + * as config/hifimanhm60x.h does. */ +#define CONFIG_STORAGE STORAGE_SD #define CONFIG_NAND NAND_RK27XX #define HAVE_SW_TONE_CONTROLS @@ -53,7 +57,7 @@ /* commented for now */ /* #define HAVE_HOTSWAP */ -#define NUM_DRIVES 2 +#define NUM_DRIVES 1 #define SECTOR_SIZE 512 /* for small(ish) SD cards */ diff --git a/firmware/export/config/hifimanhm60x.h b/firmware/export/config/hifimanhm60x.h index 51de435467..e26ff271fe 100644 --- a/firmware/export/config/hifimanhm60x.h +++ b/firmware/export/config/hifimanhm60x.h @@ -51,6 +51,7 @@ #define CONFIG_STORAGE (STORAGE_SD | STORAGE_NAND) #define CONFIG_NAND NAND_RK27XX +#define CONFIG_RK27XX_FTL RK27XX_FTL_SCHEME_B #define HAVE_SW_TONE_CONTROLS #define HAVE_SW_VOLUME_CONTROL diff --git a/firmware/export/config/hifimanhm801.h b/firmware/export/config/hifimanhm801.h index d420a1f714..c604725de1 100644 --- a/firmware/export/config/hifimanhm801.h +++ b/firmware/export/config/hifimanhm801.h @@ -45,7 +45,11 @@ /* define this if you have a flash memory storage */ #define HAVE_FLASH_STORAGE -#define CONFIG_STORAGE (STORAGE_SD | STORAGE_NAND) +/* The NAND's FTL scheme is not known yet, so the NAND is not storage here: + * only the FTL scheme finder is built (Debug > View FTL scheme). Once a + * report confirms the scheme, define CONFIG_RK27XX_FTL and add STORAGE_NAND, + * as config/hifimanhm60x.h does. */ +#define CONFIG_STORAGE STORAGE_SD #define CONFIG_NAND NAND_RK27XX #define HAVE_SW_TONE_CONTROLS @@ -55,7 +59,7 @@ /* commented for now */ /* #define HAVE_HOTSWAP */ -#define NUM_DRIVES 2 +#define NUM_DRIVES 1 #define SECTOR_SIZE 512 /* for small(ish) SD cards */ diff --git a/firmware/export/config/ihifi760.h b/firmware/export/config/ihifi760.h index b3057daaaf..e64b5403f8 100644 --- a/firmware/export/config/ihifi760.h +++ b/firmware/export/config/ihifi760.h @@ -48,8 +48,12 @@ /* define this if you have a flash memory storage */ #define HAVE_FLASH_STORAGE -#define CONFIG_STORAGE (STORAGE_SD | STORAGE_NAND) -#define NUM_DRIVES 1 /* NAND doesn't work yet */ +/* The NAND's FTL scheme is not known yet, so the NAND is not storage here: + * only the FTL scheme finder is built (Debug > View FTL scheme). Once a + * report confirms the scheme, define CONFIG_RK27XX_FTL and add STORAGE_NAND, + * as config/hifimanhm60x.h does. */ +#define CONFIG_STORAGE STORAGE_SD +#define NUM_DRIVES 1 #define CONFIG_NAND NAND_RK27XX #define HAVE_SW_TONE_CONTROLS diff --git a/firmware/export/config/ihifi770.h b/firmware/export/config/ihifi770.h index d6ca563339..0c8ddae90d 100644 --- a/firmware/export/config/ihifi770.h +++ b/firmware/export/config/ihifi770.h @@ -49,8 +49,12 @@ /* define this if you have a flash memory storage */ #define HAVE_FLASH_STORAGE -#define CONFIG_STORAGE (STORAGE_SD | STORAGE_NAND) -#define NUM_DRIVES 1 /* NAND doesn't work yet */ +/* The NAND's FTL scheme is not known yet, so the NAND is not storage here: + * only the FTL scheme finder is built (Debug > View FTL scheme). Once a + * report confirms the scheme, define CONFIG_RK27XX_FTL and add STORAGE_NAND, + * as config/hifimanhm60x.h does. */ +#define CONFIG_STORAGE STORAGE_SD +#define NUM_DRIVES 1 #define CONFIG_NAND NAND_RK27XX #define HAVE_SW_TONE_CONTROLS diff --git a/firmware/export/config/ihifi770c.h b/firmware/export/config/ihifi770c.h index c305a3a3d0..f55f120712 100644 --- a/firmware/export/config/ihifi770c.h +++ b/firmware/export/config/ihifi770c.h @@ -49,8 +49,12 @@ /* define this if you have a flash memory storage */ #define HAVE_FLASH_STORAGE -#define CONFIG_STORAGE (STORAGE_SD | STORAGE_NAND) -#define NUM_DRIVES 1 /* NAND doesn't work yet */ +/* The NAND's FTL scheme is not known yet, so the NAND is not storage here: + * only the FTL scheme finder is built (Debug > View FTL scheme). Once a + * report confirms the scheme, define CONFIG_RK27XX_FTL and add STORAGE_NAND, + * as config/hifimanhm60x.h does. */ +#define CONFIG_STORAGE STORAGE_SD +#define NUM_DRIVES 1 #define CONFIG_NAND NAND_RK27XX #define HAVE_SW_TONE_CONTROLS diff --git a/firmware/export/config/ihifi800.h b/firmware/export/config/ihifi800.h index c6e8652111..d70c259c59 100644 --- a/firmware/export/config/ihifi800.h +++ b/firmware/export/config/ihifi800.h @@ -49,8 +49,12 @@ /* define this if you have a flash memory storage */ #define HAVE_FLASH_STORAGE -#define CONFIG_STORAGE (STORAGE_SD | STORAGE_NAND) -#define NUM_DRIVES 1 /* NAND doesn't work yet */ +/* The NAND's FTL scheme is not known yet, so the NAND is not storage here: + * only the FTL scheme finder is built (Debug > View FTL scheme). Once a + * report confirms the scheme, define CONFIG_RK27XX_FTL and add STORAGE_NAND, + * as config/hifimanhm60x.h does. */ +#define CONFIG_STORAGE STORAGE_SD +#define NUM_DRIVES 1 #define CONFIG_NAND NAND_RK27XX #define HAVE_SW_TONE_CONTROLS diff --git a/firmware/export/config/ihifi960.h b/firmware/export/config/ihifi960.h index f1506f59df..a3af5d7354 100644 --- a/firmware/export/config/ihifi960.h +++ b/firmware/export/config/ihifi960.h @@ -48,8 +48,12 @@ /* define this if you have a flash memory storage */ #define HAVE_FLASH_STORAGE -#define CONFIG_STORAGE (STORAGE_SD | STORAGE_NAND) -#define NUM_DRIVES 1 /* NAND doesn't work yet */ +/* The NAND's FTL scheme is not known yet, so the NAND is not storage here: + * only the FTL scheme finder is built (Debug > View FTL scheme). Once a + * report confirms the scheme, define CONFIG_RK27XX_FTL and add STORAGE_NAND, + * as config/hifimanhm60x.h does. */ +#define CONFIG_STORAGE STORAGE_SD +#define NUM_DRIVES 1 #define CONFIG_NAND NAND_RK27XX #define HAVE_SW_TONE_CONTROLS diff --git a/firmware/export/config/ipod6g.h b/firmware/export/config/ipod6g.h index d1cb511bb5..c25192526f 100644 --- a/firmware/export/config/ipod6g.h +++ b/firmware/export/config/ipod6g.h @@ -58,6 +58,11 @@ /* define this to enable JPEG decoding */ #define HAVE_JPEG +/* S5L8702 VPU-B hardware H.264 decoder. */ +#ifndef SIMULATOR +#define HAVE_HW_H264 +#endif + /* define this if you can invert the colours on your LCD */ //#define HAVE_LCD_INVERT @@ -90,6 +95,11 @@ /* Define this if your LCD can be enabled/disabled */ #define HAVE_LCD_ENABLE +/* Define this if the target supports composite video output. */ +#ifndef SIMULATOR +#define HAVE_COMPOSITE_VIDEO_OUT +#endif + /* Define this if your LCD can be put to sleep. HAVE_LCD_ENABLE should be defined as well. */ #ifndef BOOTLOADER @@ -262,6 +272,8 @@ #ifndef LOGF_SERIAL #define IPOD_ACCESSORY_PROTOCOL #endif +/* Inline earphone remote, decoded by the jack "Mikey" controller */ +#define HAVE_MIKEY_REMOTE #endif /* Define this if you can switch on/off the accessory power supply */ diff --git a/firmware/export/config/ipodnano3g.h b/firmware/export/config/ipodnano3g.h index 9583b3b3d2..5e1b872e3a 100644 --- a/firmware/export/config/ipodnano3g.h +++ b/firmware/export/config/ipodnano3g.h @@ -80,6 +80,12 @@ #define CONFIG_STORAGE STORAGE_NAND +/* The flash controller DMAs straight into and out of the caller's buffer + * and only handles word-aligned ones: a read into a buffer 1-3 bytes off + * returns wrong data and writes past the buffer (measured). The FAT code + * bounces unaligned transfers, as on the Classic 6G. */ +#define STORAGE_NEEDS_BOUNCE_BUFFER + // TODO //#define CONFIG_NAND NAND_SAMSUNG #define CONFIG_NAND 0 @@ -88,9 +94,18 @@ needs to do cleanup on shutdown */ #define HAVE_STORAGE_FLUSH +/* Chips not validated are mounted read-only; USB hosts are told so */ +#define HAVE_STORAGE_READONLY + /* The NAND flash has 2048-byte sectors, and is our only storage */ #define SECTOR_SIZE 2048 +/* Apple's partition table on the NAND counts 4096-byte sectors */ +#define MAX_VIRT_SECTOR_SIZE 4096 + +/* If we have no valid partitions, advertise this as our sector size */ +#define DEFAULT_VIRT_SECTOR_SIZE 4096 + /* LCD dimensions */ #define LCD_WIDTH 320 #define LCD_HEIGHT 240 @@ -122,23 +137,18 @@ /* Define this to enable morse code input */ #define HAVE_MORSE_INPUT -// TODO /* define this if you have a real-time clock */ -//#define CONFIG_RTC RTC_NANO3G -#define CONFIG_RTC 0 +#define CONFIG_RTC RTC_NANO3G /* Define if the device can wake from an RTC alarm */ //#define HAVE_RTC_ALARM #define CONFIG_LCD LCD_IPOD6GNANO3G4G -// TODO -#if 0 -/* Define the type of audio codec */ +/* Define the type of audio codec. The Nano 3G has a Wolfson WM1870 at I2C + * address 0x34, which the original firmware drives as a WM8975 plus a few + * registers above the WM8975's range, so wm8975.c covers it. */ #define HAVE_WM1870 -#endif -// XXX: dummy for preliminary build, WRONG CODEC!!! -#define HAVE_CS42L55 #define HAVE_PCM_DMA_ADDRESS @@ -238,8 +248,7 @@ #define USB_VENDOR_ID 0x05AC #define USB_PRODUCT_ID 0x1262 #define USB_DEVBSS_ATTR __attribute__((aligned(32))) -// TODO -//#define HAVE_BOOTLOADER_USB_MODE +#define HAVE_BOOTLOADER_USB_MODE #ifdef BOOTLOADER #define USBPOWER_BTN_IGNORE (~0) #endif diff --git a/firmware/export/config/iriverh100.h b/firmware/export/config/iriverh100.h index e8add732de..5d9b46fec9 100644 --- a/firmware/export/config/iriverh100.h +++ b/firmware/export/config/iriverh100.h @@ -141,10 +141,6 @@ /* Define this if you want to use coldfire's i2c interface */ #define CONFIG_I2C I2C_COLDFIRE -/* Define this if you can run rockbox from flash memory */ -/* In theory we can, but somebody needs to verify there are no issues. */ -#define HAVE_FLASHED_ROCKBOX - /* define this if the hardware can be powered off while charging */ #define HAVE_POWEROFF_WHILE_CHARGING diff --git a/firmware/export/config/iriverh120.h b/firmware/export/config/iriverh120.h index b591b10e83..00fba08361 100644 --- a/firmware/export/config/iriverh120.h +++ b/firmware/export/config/iriverh120.h @@ -148,9 +148,6 @@ /* Define this if you want to use coldfire's i2c interface */ #define CONFIG_I2C I2C_COLDFIRE -/* Define this if you can run rockbox from flash memory */ -#define HAVE_FLASHED_ROCKBOX - /* define this if the hardware can be powered off while charging */ #define HAVE_POWEROFF_WHILE_CHARGING diff --git a/firmware/export/config/iriverh300.h b/firmware/export/config/iriverh300.h index 4b93c62cec..572210e8f7 100644 --- a/firmware/export/config/iriverh300.h +++ b/firmware/export/config/iriverh300.h @@ -157,9 +157,6 @@ /* Define this if you want to use coldfire's i2c interface */ #define CONFIG_I2C I2C_COLDFIRE -/* Define this if you can run rockbox from flash memory */ -#define HAVE_FLASHED_ROCKBOX - /* The size of the flash ROM */ #define FLASH_SIZE 0x400000 diff --git a/firmware/export/config/rk27generic.h b/firmware/export/config/rk27generic.h index 6ad98756ed..a61f0400ac 100644 --- a/firmware/export/config/rk27generic.h +++ b/firmware/export/config/rk27generic.h @@ -56,14 +56,39 @@ #define HAVE_FLASH_STORAGE #define CONFIG_STORAGE (STORAGE_SD | STORAGE_NAND) -#define NUM_DRIVES 1 /* NAND doesn't work yet */ #define CONFIG_NAND NAND_RK27XX +#define CONFIG_RK27XX_FTL RK27XX_FTL_SCHEME_A #define HAVE_SW_TONE_CONTROLS /* commented for now */ /* #define HAVE_HOTSWAP */ +/* The NAND holds two volumes (ftl-scheme-a.c): SYS, ~94 MB, where the + * original firmware keeps itself - on a Rockbox device that includes the + * BASE.RKW that chainloads the Rockbox bootloader - and USER, the rest of the + * chip. The split is recorded in the boot area, so they are separate drives + * rather than partitions of one. + * + * SYS is hidden: nothing in it is the user's, and deleting or overfilling it + * stops the device booting. Define HAVE_RK27XX_NAND_SYS - here, or in a + * purpose build - to present it as a drive of its own, readable and, with + * FTL_ALLOW_WRITE, writable like USER. + * + * storage.c numbers drives by driver, SD first: + * + * default HAVE_RK27XX_NAND_SYS + * drive 0 SD SD + * drive 1 NAND USER NAND SYS + * drive 2 NAND USER + */ +/* #define HAVE_RK27XX_NAND_SYS */ + +#ifdef HAVE_RK27XX_NAND_SYS +#define NUM_DRIVES 3 +#else +#define NUM_DRIVES 2 +#endif #define SECTOR_SIZE 512 /* for small(ish) SD cards */ @@ -109,8 +134,12 @@ /* Define the type of audio codec */ #define HAVE_RK27XX_CODEC +/* The tuner is an RDA5807P, driven in its TEA5767 compatible mode as the + * original firmware does - it has no RDS, and its own mode brings nothing */ #define CONFIG_TUNER TEA5767 #define CONFIG_TUNER_XTAL 32768 +/* its audio is on the codec's line input 1 */ +#define RK27XX_CODEC_FM_LINE 1 /* #define HAVE_PCM_DMA_ADDRESS */ diff --git a/firmware/export/config/samsungypcp3.h b/firmware/export/config/samsungypcp3.h new file mode 100644 index 0000000000..5b8a8d1803 --- /dev/null +++ b/firmware/export/config/samsungypcp3.h @@ -0,0 +1,195 @@ +/* + * This config file is for the Samsung YP-CP3 (rk27xx) + */ + +/* For Rolo and boot loader */ +#define MODEL_NUMBER 132 + +#define MODEL_NAME "Samsung YP-CP3" + +/* define this if you have recording possibility */ +#define HAVE_RECORDING + +/* define the bitmask of hardware sample rates */ +#define HW_SAMPR_CAPS (SAMPR_CAP_44 | SAMPR_CAP_22 | SAMPR_CAP_11 \ + | SAMPR_CAP_48 | SAMPR_CAP_24 | SAMPR_CAP_12 \ + | SAMPR_CAP_32 | SAMPR_CAP_16 | SAMPR_CAP_8) + +/* define the bitmask of recording sample rates */ +#define REC_SAMPR_CAPS (SAMPR_CAP_44 | SAMPR_CAP_22 | SAMPR_CAP_11 \ + | SAMPR_CAP_48 | SAMPR_CAP_24 | SAMPR_CAP_12 \ + | SAMPR_CAP_32 | SAMPR_CAP_16 | SAMPR_CAP_8) + +/* Line gain, used for the FM radio, in 0.01 dB: start at the +12 dB the + * radio is heard at when not recording (wm8751.c), so recording it is as + * loud as listening to it */ +#define DEFAULT_REC_LEFT_GAIN 1200 +#define DEFAULT_REC_RIGHT_GAIN 1200 + +/* define this if you have a colour LCD */ +#define HAVE_LCD_COLOR + +/* define this if you want album art for this target */ +#define HAVE_ALBUMART + +/* define this to enable bitmap scaling */ +#define HAVE_BMP_SCALING + +/* define this to enable JPEG decoding */ +#define HAVE_JPEG + +/* define this if you have access to the quickscreen */ +#define HAVE_QUICKSCREEN + +/* define this if you would like tagcache to build on this target */ +#define HAVE_TAGCACHE + +/* define this if you have a flash memory storage */ +#define HAVE_FLASH_STORAGE + +#define CONFIG_STORAGE (STORAGE_SD | STORAGE_NAND) + +#define CONFIG_NAND NAND_RK27XX +#define CONFIG_RK27XX_FTL RK27XX_FTL_SCHEME_A + +/* commented for now */ +/* #define HAVE_HOTSWAP */ + +/* The NAND holds two volumes, SYS and USER; SYS is hidden - see + * config/rk27generic.h for why and for what HAVE_RK27XX_NAND_SYS does. + * storage.c numbers drives by driver, SD first: + * + * default HAVE_RK27XX_NAND_SYS + * drive 0 SD SD + * drive 1 NAND USER NAND SYS + * drive 2 NAND USER + */ +/* #define HAVE_RK27XX_NAND_SYS */ + +#ifdef HAVE_RK27XX_NAND_SYS +#define NUM_DRIVES 3 +#else +#define NUM_DRIVES 2 +#endif +#define SECTOR_SIZE 512 + +/* for small(ish) SD cards */ +#define HAVE_FAT16SUPPORT + +/* LCD dimensions */ +#define LCD_WIDTH 400 +#define LCD_HEIGHT 240 +/* sqrt(400^2 + 240^2) / 3.0 = 155.5 */ +#define LCD_DPI 155 +#define LCD_DEPTH 16 /* pseudo 262.144 colors */ +#define LCD_PIXELFORMAT RGB565 /* rgb565 */ + +/* Define this if your LCD can be put to sleep. HAVE_LCD_ENABLE + should be defined as well. */ +#ifndef BOOTLOADER +/* TODO: #define HAVE_LCD_SLEEP */ +/* TODO: #define HAVE_LCD_SLEEP_SETTING */ +#endif + +/* the YP-R0's key set - see ypcp3/button-target.h */ +#define CONFIG_KEYPAD SAMSUNG_YPR0_PAD + +/* Define this to enable morse code input */ +#define HAVE_MORSE_INPUT + +#define CONFIG_LCD LCD_SPFD5420A + +/* Wolfson WM8750 on I2C, headphones on its OUT2. The codec is the I2S + * master, driving BCLK and both of its LRCKs: as a slave it frames the ADC + * on its ADCLRC input, which the rk27xx's one LRCK does not reach, and + * records zeros. Its MCLK comes from the rk27xx's codec PLL at 256 fs for + * every rate (pcm-rk27xx.c), so the codec's CLOCKING register is its + * normal-mode 256 fs setting throughout. The original firmware runs the + * codec as master off a fixed 12 MHz instead, in USB mode, which puts + * 44.1 kHz at 44.118. */ +#define HAVE_WM8750 +#define RK27XX_I2S_MCLK +#define CODEC_SRCTRL_11025HZ 0 +#define CODEC_SRCTRL_22050HZ 0 +#define CODEC_SRCTRL_44100HZ 0 + +/* Seiko S-35390A real-time clock on I2C, left in 12-hour mode by the + * original firmware */ +#define CONFIG_RTC RTC_S35390A + +/* Silicon Labs Si4703 FM tuner on I2C, with RDS. It has no power control - + * the original firmware has none either - and its audio reaches the codec + * on LINPUT1/RINPUT1. RDS is polled, which needs no interrupt line. */ +#define CONFIG_TUNER SI4700 +#define HAVE_RDS_CAP +#define CONFIG_RDS (RDS_CFG_POLL | RDS_CFG_PROCESS) +#define CONFIG_RDS_POLL_TICKS 4 +/* inputs: the microphone (mono, on RINPUT2) and the tuner */ +#define INPUT_SRC_CAPS (SRC_CAP_MIC | SRC_CAP_FMRADIO) + +/* Define this for LCD backlight available */ +#define HAVE_BACKLIGHT +#define HAVE_BACKLIGHT_BRIGHTNESS +#define MIN_BRIGHTNESS_SETTING 0 +#define MAX_BRIGHTNESS_SETTING 31 +#define DEFAULT_BRIGHTNESS_SETTING 20 +#define CONFIG_BACKLIGHT_FADING BACKLIGHT_FADING_SW_HW_REG + +/* Define this if you have a software controlled poweroff */ +#define HAVE_SW_POWEROFF + +/* The number of bytes reserved for loadable codecs */ +#define CODEC_SIZE 0x100000 + +/* The number of bytes reserved for loadable plugins */ +#define PLUGIN_BUFFER_SIZE 0x80000 + +/* TODO: Figure out real values */ +#define BATTERY_CAPACITY_DEFAULT 400 /* default battery capacity */ +#define BATTERY_CAPACITY_MIN 300 /* min. capacity selectable */ +#define BATTERY_CAPACITY_MAX 500 /* max. capacity selectable */ +#define BATTERY_CAPACITY_INC 10 /* capacity increment */ + +#define CONFIG_BATTERY_MEASURE VOLTAGE_MEASURE + +/* Hardware controlled charging with monitoring */ +#define CONFIG_CHARGING CHARGING_MONITOR + +/* define this if the unit can be powered or charged via USB */ +#define HAVE_USB_POWER + +/* USB On-the-go */ +#define CONFIG_USBOTG USBOTG_RK27XX + +/* enable these for the experimental usb stack */ +#define HAVE_USBSTACK + +#define USB_VENDOR_ID 0x071b +#define USB_PRODUCT_ID 0x3202 +#define HAVE_BOOTLOADER_USB_MODE + +/* The exact type of CPU */ +#define CONFIG_CPU RK27XX + +/* I2C interface */ +#define CONFIG_I2C I2C_RK27XX + +/* Define this to the CPU frequency */ +#define CPU_FREQ 200000000 + +/* Offset ( in the firmware file's header ) to the file CRC */ +#define FIRMWARE_OFFSET_FILE_CRC 0 + +/* Offset ( in the firmware file's header ) to the real data */ +#define FIRMWARE_OFFSET_FILE_DATA 8 + +/* Define this if you have adjustable CPU frequency */ +#define HAVE_ADJUSTABLE_CPU_FREQ + +/* Virtual LED (icon) */ +#define CONFIG_LED LED_VIRTUAL + +#define RKW_FORMAT +#define BOOTFILE_EXT "rkw" +#define BOOTFILE "rockbox." BOOTFILE_EXT +#define BOOTDIR "/.rockbox" diff --git a/firmware/export/config/shanlingq1.h b/firmware/export/config/shanlingq1.h index 289f090187..6b8ef7fb16 100644 --- a/firmware/export/config/shanlingq1.h +++ b/firmware/export/config/shanlingq1.h @@ -67,6 +67,9 @@ #define STORAGE_WANTS_ALIGN #define STORAGE_NEEDS_BOUNCE_BUFFER +/* One SD card slot */ +#define SDMMC_HOST_NUM_SD_CONTROLLERS 1 + /* RTC settings */ #define CONFIG_RTC RTC_X1000 /* TODO: implement HAVE_RTC_ALARM */ diff --git a/firmware/export/config/sim.h b/firmware/export/config/sim.h index b4ba80a87a..fe9194da35 100644 --- a/firmware/export/config/sim.h +++ b/firmware/export/config/sim.h @@ -77,8 +77,6 @@ #undef INCLUDE_TIMEOUT_API -#undef HAVE_FLASHED_ROCKBOX - #undef IPOD_ACCESSORY_PROTOCOL #undef HAVE_WHEEL_POSITION diff --git a/firmware/export/debug.h b/firmware/export/debug.h index 0cda5618bd..114f0b951a 100644 --- a/firmware/export/debug.h +++ b/firmware/export/debug.h @@ -34,8 +34,8 @@ extern void ldebugf(const char* file, int line, const char *fmt, ...) || (defined(APPLICATION) && defined(DEBUG)) #define DEBUGF debugf #define LDEBUGF(...) ldebugf(__FILE__, __LINE__, __VA_ARGS__) -#elif (CONFIG_PLATFORM & PLATFORM_CTRU) -/* let's use second display for debug output */ +#elif (CONFIG_PLATFORM & PLATFORM_GAME_CONSOLE) +/* always keep debugf available for console platforms */ #define DEBUGF debugf #define LDEBUGF(...) ldebugf(__FILE__, __LINE__, __VA_ARGS__) #elif defined(DEBUG) /* DEBUG on native targets */ diff --git a/firmware/export/hw_h264.h b/firmware/export/hw_h264.h new file mode 100644 index 0000000000..affb132978 --- /dev/null +++ b/firmware/export/hw_h264.h @@ -0,0 +1,47 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2025-2026 David Cormier + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#ifndef HW_H264_H +#define HW_H264_H + +#include +#include + +struct vpu_h264; + +/* The container, audio, synchronization, and UI layers live in the plugin. + * Only the target hardware decoder is exported by core. */ +struct hw_h264_api +{ + size_t (*buf_size)(int max_w, int max_h); + struct vpu_h264 *(*open)(void *buf, size_t buf_size, + int max_w, int max_h); + int (*configure)(struct vpu_h264 *decoder, + const uint8_t *avcc, int avcc_len); + int (*decode_sample)(struct vpu_h264 *decoder, const uint8_t *sample, + int sample_len, int nalu_len_size); + void (*get_frame)(const struct vpu_h264 *decoder, + const uint8_t **y, const uint8_t **cb, + const uint8_t **cr, int *w, int *h, int *stride); + void (*close)(struct vpu_h264 *decoder); +}; + +extern const struct hw_h264_api target_hw_h264_api; + +#endif /* HW_H264_H */ diff --git a/firmware/export/nand.h b/firmware/export/nand.h index bf597f5f6a..43e24da961 100644 --- a/firmware/export/nand.h +++ b/firmware/export/nand.h @@ -45,6 +45,9 @@ int nand_spinup_time(void); /* ticks */ #ifdef STORAGE_GET_INFO void nand_get_info(IF_MD(int drive,) struct storage_info *info); #endif +#ifdef HAVE_STORAGE_READONLY +bool nand_readonly(IF_MD_NONVOID(int drive)); +#endif long nand_last_disk_activity(void); diff --git a/firmware/export/pcm-internal.h b/firmware/export/pcm-internal.h index 4f69f77806..7ffbb08b36 100644 --- a/firmware/export/pcm-internal.h +++ b/firmware/export/pcm-internal.h @@ -45,21 +45,22 @@ #define PCM_FACTOR_UNITY (1u << PCM_SW_VOLUME_FRACBITS) #endif /* PCM_SW_VOLUME_FRACBITS */ +#ifdef HAVE_SDL_AUDIO #ifdef PCM_SW_VOLUME_UNBUFFERED /* Copies buffer with volume scaling applied */ void pcm_sw_volume_copy_buffer(void *dst, const void *src, size_t size); #define pcm_copy_buffer pcm_sw_volume_copy_buffer -#else /* !PCM_SW_VOLUME_UNBUFFERED */ -#ifdef HAVE_SDL_AUDIO +#else /* PCM_SW_VOLUME_UNBUFFERED */ #define pcm_copy_buffer memcpy -#endif +#endif /* PCM_SW_VOLUME_UNBUFFERED */ +#endif /* HAVE_SDL_AUDIO */ + #ifndef PCM_PLAY_DBL_BUF_SAMPLES #define PCM_PLAY_DBL_BUF_SAMPLES 1024 /* Max 4KByte chunks */ #endif #ifndef PCM_DBL_BUF_BSS #define PCM_DBL_BUF_BSS /* In DRAM, uncached may be better */ #endif -#endif /* PCM_SW_VOLUME_UNBUFFERED */ void pcm_sync_pcm_factors(void); #endif /* HAVE_SW_VOLUME_CONTROL */ @@ -126,20 +127,15 @@ static FORCE_INLINE enum pcm_dma_status pcm_play_call_status_cb( static FORCE_INLINE enum pcm_dma_status pcm_play_dma_status_callback(enum pcm_dma_status status) { -#if defined(HAVE_SW_VOLUME_CONTROL) && !defined(PCM_SW_VOLUME_UNBUFFERED) +#ifdef WANT_SWVOL extern enum pcm_dma_status - pcm_play_dma_status_callback_int(enum pcm_dma_status status); - return pcm_play_dma_status_callback_int(status); -#else + pcm_play_dma_status_callback_int_swvol(enum pcm_dma_status status); + if (pcm_current_sink_caps()->volume_type == PCM_SINK_SWVOL) + return pcm_play_dma_status_callback_int_swvol(status); +#endif /* WANT_SWVOL */ return pcm_play_call_status_cb(status); -#endif /* HAVE_SW_VOLUME_CONTROL && !PCM_SW_VOLUME_UNBUFFERED */ } -#if defined(HAVE_SW_VOLUME_CONTROL) && !defined(PCM_SW_VOLUME_UNBUFFERED) -void pcm_play_dma_start_int(const void *addr, size_t size); -void pcm_play_dma_stop_int(void); -#endif /* HAVE_SW_VOLUME_CONTROL && !PCM_SW_VOLUME_UNBUFFERED */ - /* Called by the bottom layer ISR when more data is needed. Returns true * if a new buffer is available, false otherwise. */ bool pcm_play_dma_complete_callback(enum pcm_dma_status status, diff --git a/firmware/export/pcm_mixer.h b/firmware/export/pcm_mixer.h index a003100285..bbc2c2c9cd 100644 --- a/firmware/export/pcm_mixer.h +++ b/firmware/export/pcm_mixer.h @@ -71,7 +71,7 @@ enum pcm_mixer_channel { PCM_MIXER_CHAN_PLAYBACK = 0, -#ifdef USB_ENABLE_AUDIO +#if defined(USB_ENABLE_AUDIO) || defined(HAVE_HOST_USB_AUDIO) PCM_MIXER_CHAN_USBAUDIO, #endif PCM_MIXER_CHAN_VOICE, diff --git a/firmware/export/pcm_sink.h b/firmware/export/pcm_sink.h index 522a5aa853..c78d4af617 100644 --- a/firmware/export/pcm_sink.h +++ b/firmware/export/pcm_sink.h @@ -23,10 +23,16 @@ #include "config.h" +#define PCM_SINK_HWVOL 0 +#ifdef WANT_SWVOL +#define PCM_SINK_SWVOL 1 +#endif + struct pcm_sink_caps { const unsigned long* samprs; - uint16_t num_samprs; - uint16_t default_freq; + uint8_t num_samprs; + uint8_t default_freq; + uint8_t volume_type; }; struct pcm_sink_ops { @@ -41,7 +47,7 @@ struct pcm_sink_ops { struct pcm_sink { /* characteristics */ - const struct pcm_sink_caps caps; + struct pcm_sink_caps caps; /* operations */ const struct pcm_sink_ops ops; diff --git a/firmware/export/pcm_sw_volume.h b/firmware/export/pcm_sw_volume.h index 5aba259b2a..dc9962fbb5 100644 --- a/firmware/export/pcm_sw_volume.h +++ b/firmware/export/pcm_sw_volume.h @@ -26,10 +26,11 @@ ** the library or a hardware necessity. Normally, it shouldn't be used and ** only the port developer can properly decide. **/ -#ifdef HAVE_SW_VOLUME_CONTROL +#ifdef WANT_SWVOL #include #include +#include #define PCM_MUTE_LEVEL INT_MIN @@ -41,6 +42,11 @@ void pcm_set_prescaler(int prescale); /* Set the per-channel volume cut/gain for all PCM playback */ void pcm_set_master_volume(int vol_l, int vol_r); -#endif /* HAVE_SW_VOLUME_CONTROL */ +void pcm_play_dma_start_int_swvol(const void *addr, size_t size); +void pcm_play_dma_stop_int_swvol(void); +enum pcm_dma_status pcm_play_dma_status_callback_int_swvol(enum pcm_dma_status status); +bool pcm_play_dma_complete_callback_swvol(enum pcm_dma_status status, + const void **addr, size_t *size); +#endif /* WANT_SWVOL */ #endif /* PCM_SW_VOLUME_H */ diff --git a/firmware/export/rbpaths.h b/firmware/export/rbpaths.h index 08691eb0a3..de4a3bbb71 100644 --- a/firmware/export/rbpaths.h +++ b/firmware/export/rbpaths.h @@ -40,7 +40,7 @@ /* NOTE: target-specific hosted HOME_DIR resides in filesystem-app.c */ #if !defined(APPLICATION) || defined(SAMSUNG_YPR0) || defined(SAMSUNG_YPR1) || \ defined(DX50) || defined(DX90) || defined(SONY_NWZ_LINUX) || \ - defined(HIBY_LINUX) || defined(FIIO_M3K_LINUX) || defined(CTRU) + defined(HIBY_LINUX) || defined(FIIO_M3K_LINUX) || defined(N3DS) #define HOME_DIR "/" @@ -87,7 +87,7 @@ #if defined(APPLICATION) && \ !(defined(SAMSUNG_YPR0) || defined(SAMSUNG_YPR1) || \ defined(DX50) || defined(DX90) || defined(SONY_NWZ_LINUX) || \ - defined(HIBY_LINUX) || defined(FIIO_M3K_LINUX) || defined(CTRU)) + defined(HIBY_LINUX) || defined(FIIO_M3K_LINUX) || defined(N3DS)) #define PLUGIN_DATA_DIR ROCKBOX_DIR "/rocks.data" #define PLUGIN_GAMES_DATA_DIR PLUGIN_DATA_DIR diff --git a/firmware/export/rk27xx.h b/firmware/export/rk27xx.h index 8ab94add82..5ec71f3f8c 100644 --- a/firmware/export/rk27xx.h +++ b/firmware/export/rk27xx.h @@ -429,6 +429,8 @@ #define GPIO_PECON (*(volatile unsigned long *)(APB0_GPIO1 + 0x04)) #define GPIO_PFDR (*(volatile unsigned long *)(APB0_GPIO1 + 0x08)) #define GPIO_PFCON (*(volatile unsigned long *)(APB0_GPIO1 + 0x0C)) +/* undocumented, kept over a reset: the OF leaves its boot request here */ +#define GPIO1_BOOTMODE (*(volatile unsigned long *)(APB0_GPIO1 + 0x1C)) #define GPIO1_TEST (*(volatile unsigned long *)(APB0_GPIO1 + 0x20)) #define GPIO_IEE (*(volatile unsigned long *)(APB0_GPIO1 + 0x24)) @@ -919,14 +921,35 @@ #define FL_RST (1<<0) #define BCHCTL (*(volatile unsigned long *)(AHB0_NANDC + 0x0C)) -/* bit 13 is used but unknown */ +/* ECC strength select: clear = t=8, set = t=14. Both are m=13, poly 0x25af. + * The OF uses t=8 for the boot region and for Samsung media, t=14 for the + * data area on some devices. Established by decoding both modes against real + * media until the stored ECC bytes reproduced. */ +#define BCH_T14 (1<<13) /* bit 12 is used but unknown */ #define BCH_WR (1<<1) #define BCH_RST (1<<0) #define BCHST (*(volatile unsigned long *)(AHB0_NANDC + 0xD0)) -/* bit 2 ERR ?? */ -/* bit 0 ?? */ +/* Read once FLCTL reports FL_RDY for the sector: + * + * bit 0 result valid + * bit 2 error - UNCORRECTABLE when set together with bit 0 + * bits 6:3 number of corrected bit errors + * + * From the rk2705 NAND bootloader's ECC read loop: + * tst r0,#1 ; tst r0,#4 both set -> sector is uncorrectable + * lsl r0,#25 ; lsr r0,#28 -> (BCHST >> 3) & 0xf, corrected bits + * cmp r0,#3 >= 3 triggers a block refresh + * The threshold agrees with MlcRefreshHook in the Samsung OF's flash.o, i.e. + * two independent firmwares. */ +#define BCH_VALID (1<<0) +#define BCH_ERR (1<<2) +#define BCH_CORRECTED(st) (((st) >> 3) & 0xf) +#define BCH_UNCORRECTABLE(st) \ + (((st) & (BCH_VALID|BCH_ERR)) == (BCH_VALID|BCH_ERR)) +/* corrected-bit count at which the OF schedules a block refresh */ +#define BCH_REFRESH_THRESHOLD 3 #define FLASH_DATA(n) (*(volatile unsigned char *)(AHB0_NANDC + 0x200 + (n<<9))) #define FLASH_ADDR(n) (*(volatile unsigned char *)(AHB0_NANDC + 0x204 + (n<<9))) diff --git a/firmware/export/rkw-loader.h b/firmware/export/rkw-loader.h index 2101bd03b7..076aca2eba 100644 --- a/firmware/export/rkw-loader.h +++ b/firmware/export/rkw-loader.h @@ -30,7 +30,9 @@ struct rkw_header_t { uint32_t image_base; /* Base address of the firmware image */ uint32_t load_address; /* Load address */ uint32_t load_limit; /* End of the firmware image */ - uint32_t bss_start; /* This is the start of .bss section of the firmware I suppose */ + uint32_t handoff; /* Where the loader leaves its three handoff + * words for the image (see rkw-loader.c) + */ uint32_t reserved0; /* reserved - I've seen only zeros in this field so far */ uint32_t reserved1; /* reserved - I've seen only zeros in this field so far */ uint32_t entry_point; /* Entry point address */ diff --git a/firmware/export/s5l87xx.h b/firmware/export/s5l87xx.h index 3b13fd72e2..6d51282ae1 100644 --- a/firmware/export/s5l87xx.h +++ b/firmware/export/s5l87xx.h @@ -870,6 +870,9 @@ #define FMC_BASE 0x3C200000 #elif CONFIG_CPU==S5L8701 #define FMC_BASE 0x39400000 +#elif CONFIG_CPU==S5L8702 +/* The first of two controllers; the BootROM addresses the second at +0x400 */ +#define FMC_BASE 0x38a00000 #endif #define FMCTRL0 (*(REG32_PTR_T)(FMC_BASE + 0x0000)) /* Control Register0 */ @@ -925,8 +928,21 @@ #define RSSYND3_1 (*(REG32_PTR_T)(FMC_BASE + 0x0184)) /* On-the-fly Synd Register3[63:32] */ #define RSSYND3_2 (*(REG32_PTR_T)(FMC_BASE + 0x0188)) /* On-the-fly Synd Register3[71:64] */ #define FLAGSYND (*(REG32_PTR_T)(FMC_BASE + 0x0190)) /* On-the-fly ECC Result Flag */ + +/* S5L8702 registers the BootROM's page read uses. + * The real names are unknown. */ +#define FMUNK78 (*(REG32_PTR_T)(FMC_BASE + 0x0078)) /* spare decode setup, 0x5140 */ +#define FMUNK7C (*(REG32_PTR_T)(FMC_BASE + 0x007C)) /* bit 1 start/busy; FMSYND5..7 */ +#define FMTRANS0 (*(REG32_PTR_T)(FMC_BASE + 0x080C)) /* per-chunk ECC correction kick */ +#define FMUNK810 (*(REG32_PTR_T)(FMC_BASE + 0x0810)) /* bit 0: correction failed, inferred */ +#define FMTRANS1 (*(REG32_PTR_T)(FMC_BASE + 0x0814)) /* correction mode, always 0x01000180 */ +#define FMTRANSSTAT (*(REG32_PTR_T)(FMC_BASE + 0x0840)) /* correction status, w1c; bit 2 done */ + +#define FMCTRL0_CE(bank) (1 << ((bank) + 1)) /* chip enable bits 1-8 */ +#define FMCTRL0_CE_MASK (0xff << 1) #define FMCTRL0_ENABLEDMA (1 << 10) #define FMCTRL0_UNK1 (1 << 11) +#define FMCTRL0_AUTOXFER (1 << 24) /* S5L8702: write the chunk to the address in FMDATAW0 */ #define FMCTRL1_DOTRANSADDR (1 << 0) #define FMCTRL1_DOREADDATA (1 << 1) #define FMCTRL1_DOWRITEDATA (1 << 2) @@ -940,6 +956,9 @@ #define FMCSTAT_BANK1READY (1 << 5) #define FMCSTAT_BANK2READY (1 << 6) #define FMCSTAT_BANK3READY (1 << 7) +#define FMCSTAT_UNK20 (1 << 20) /* S5L8702: chunk written to memory */ +#define FMCSTAT_STATUSREADY (1 << 23) /* S5L8702: set after READ STATUS; not a ready bit */ +#define FMCSTAT_UNK27 (1 << 27) /* S5L8702: chunk has ECC errors (inferred) */ /* 13. SECURE DIGITAL CARD INTERFACE (SDCI) */ #if CONFIG_CPU==S5L8700 || CONFIG_CPU==S5L8701 @@ -1485,6 +1504,79 @@ #define LCD_DRV_RST (*(REG32_PTR_T)(LCD_BASE + 0x28)) /* Reset drive signal */ #define LCD_WDATA (*(REG32_PTR_T)(LCD_BASE + 0x40)) /* Write data register (0x40...0x5C) FIXME */ +#if CONFIG_CPU == S5L8702 +/* Composite video pipeline: video processor, mixer/router, and encoder. */ +#define SVID_COMPOSITOR_BASE 0x39100000u +#define SVID_ROUTER_BASE 0x39200000u +#define SVID_ENCODER_BASE 0x39300000u + +#define SVID_REG(base, offset) \ + (*(REG32_PTR_T)((uintptr_t)(base) + (offset))) + +#define SVID_VP_ENABLE 0x000u +#define SVID_VP_MODE 0x010u +#define SVID_VP_PLANE0_PTR 0x028u +#define SVID_VP_PLANE2_PTR 0x02cu +#define SVID_VP_PLANE1_PTR 0x030u +#define SVID_VP_UNUSED_PTR 0x034u +#define SVID_VP_IMG_WIDTH 0x03cu +#define SVID_VP_IMG_HEIGHT 0x040u +#define SVID_VP_SRC_H_POS 0x044u +#define SVID_VP_SRC_V_POS 0x048u +#define SVID_VP_SRC_WIDTH 0x04cu +#define SVID_VP_SRC_HEIGHT 0x050u +#define SVID_VP_DST_H_POS 0x054u +#define SVID_VP_DST_V_POS 0x058u +#define SVID_VP_DST_WIDTH 0x05cu +#define SVID_VP_DST_HEIGHT 0x060u +#define SVID_VP_H_RATIO 0x064u +#define SVID_VP_V_RATIO 0x068u +#define SVID_VP_PLANE_MODE 0x3c0u +#define SVID_VP_LUMA_SPAN 0x3c4u +#define SVID_VP_CHROMA_SPAN 0x3c8u +#define SVID_VP_ENDIAN_MODE 0x3ccu + +#define SVID_VP_ENABLE_ON (1u << 0) +#define SVID_VP_MODE_RETAIL_FMT8 0u +#define SVID_VP_PLANE_PLANAR 1u +#define SVID_VP_ENDIAN_LITTLE (1u << 0) + +#define SVID_MXR_STATUS 0x000u +#define SVID_MXR_CONFIG 0x004u +#define SVID_MXR_VIDEO_CONFIG 0x008u +#define SVID_MXR_GRAPHIC0_CONFIG 0x00cu +#define SVID_MXR_GRAPHIC0_BASE 0x010u +#define SVID_MXR_GRAPHIC0_POS 0x014u +#define SVID_MXR_GRAPHIC0_SIZE 0x018u +#define SVID_MXR_GRAPHIC_FORMATS 0x040u +#define SVID_MXR_BG_COLOR0 0x048u +#define SVID_MXR_BG_COLOR1 0x04cu +#define SVID_MXR_BG_COLOR2 0x050u +#define SVID_MXR_COMMIT 0x800u + +#define SVID_MXR_STATUS_IDLE (1u << 1) +#define SVID_MXR_STATUS_SYNC (1u << 2) +#define SVID_MXR_STATUS_RUN (1u << 0) +#define SVID_MXR_GRAPHICS_ENABLE (1u << 3) +#define SVID_MXR_VIDEO_ENABLE (1u << 4) +#define SVID_MXR_GRAPHIC4_ENABLE (1u << 5) +#define SVID_MXR_SD_SCAN (1u << 1) +#define SVID_MXR_ENABLE_MASK (SVID_MXR_GRAPHICS_ENABLE | \ + SVID_MXR_VIDEO_ENABLE | \ + SVID_MXR_GRAPHIC4_ENABLE) + +#define SVID_SDO_CLOCK 0x000u +#define SVID_SDO_CONFIG 0x008u +#define SVID_SDO_DAC 0x03cu +#define SVID_SDO_SOFTWARE_RESET (1u << 4) +#define SVID_SDO_CLOCK_ON (1u << 0) +#define SVID_SDO_DAC_ALL_ON 7u +#define SVID_SDO_DAC_MUX_MASK (0x3fu << 8) +#define SVID_SDO_COMPONENT (1u << 6) +#define SVID_SDO_PROGRESSIVE (1u << 4) +#define SVID_SDO_STANDARD_MASK 0xfu +#endif /* CONFIG_CPU == S5L8702 */ + /* 27. CLCD CONTROLLER */ #define LCDBASE 0x39200000 @@ -1570,6 +1662,28 @@ The following peripherals are not present in the Samsung S5L8700 datasheet. Information for them was gathered solely by reverse-engineering Apple's firmware. */ +/* VPU-B H.264 decoder - S5L8702 */ +#if CONFIG_CPU == S5L8702 +#define VPU_MODE (*((REG32_PTR_T)(0x38100314))) +#define VPU_BASE 0x39800000 +#define VPU_REG(off) (*((REG32_PTR_T)(VPU_BASE + (off)))) + +#define VPU_DPB_Y(i) VPU_REG((i) * 12) +#define VPU_DPB_CB(i) VPU_REG((i) * 12 + 4) +#define VPU_DPB_CR(i) VPU_REG((i) * 12 + 8) +#define VPU_OUT_Y VPU_REG(0x0cc) +#define VPU_OUT_CB VPU_REG(0x0d0) +#define VPU_OUT_CR VPU_REG(0x0d4) +#define VPU_CTRL_BUF VPU_REG(0x0d8) +#define VPU_SLICE_DESC VPU_REG(0x0dc) +#define VPU_DIMS VPU_REG(0x0e0) +#define VPU_STRIDES VPU_REG(0x0e4) +#define VPU_CTRL VPU_REG(0x0e8) +#define VPU_STATUS0 VPU_REG(0x0f0) +#define VPU_STATUS1 VPU_REG(0x0f4) +#define VPU_CONFIG VPU_REG(0x118) +#endif + /* Hardware AES crypto unit - S5L8701+ */ #if CONFIG_CPU==S5L8701 #define AES_BASE 0x39800000 diff --git a/firmware/export/sdmmc.h b/firmware/export/sdmmc.h index 8dbded62b9..c916d31ed9 100644 --- a/firmware/export/sdmmc.h +++ b/firmware/export/sdmmc.h @@ -34,6 +34,7 @@ typedef struct unsigned long ocr; /* OCR register */ unsigned long csd[4]; /* CSD register, 16 bytes */ unsigned long cid[4]; /* CID register, 16 bytes */ + unsigned long scr[2]; /* SCR register, 8 bytes */ unsigned long speed; /* bit/s */ unsigned int nsac; /* clock cycles */ unsigned long taac; /* n * 0.1 ns */ diff --git a/firmware/export/sdmmc_host.h b/firmware/export/sdmmc_host.h index 30419faa0a..429ff8fa4c 100644 --- a/firmware/export/sdmmc_host.h +++ b/firmware/export/sdmmc_host.h @@ -232,12 +232,28 @@ struct sdmmc_host */ struct mutex lock; + /* Temporary disk cache buffer */ + void *dc_buffer; + /* Bus & device state flags; must only be accessed with lock held */ bool enabled : 1; bool need_reset : 1; bool powered : 1; bool initialized : 1; bool is_hcs_card : 1; + bool use_cmd23 : 1; + bool led_active : 1; /* LED active may be read without lock */ + + /* + * Some 512G Samsung cards (and possibly other cards) seem to + * have problems with repeated single block read commands and + * sometimes just time out without ever sending the data. The + * card response is OK but no data is received. + * + * Adding a delay of a few tens of milliseconds before each + * read/write single block command appears to fix the problem. + */ + bool quirk_rdwrsingleblock_delay : 1; /* Controller implemented by the target */ const struct sdmmc_controller_ops *ops; @@ -291,4 +307,10 @@ void sdmmc_host_init_medium_present(struct sdmmc_host *host, bool present) INIT_ */ void sdmmc_host_set_medium_present(struct sdmmc_host *host, bool present); +/** + * Returns clock frequency in Hz for an `SDMMC_BUS_CLOCK_*` constant. + * Returns zero if fed an invalid constant. + */ +size_t sdmmc_host_get_bus_freq(uint32_t clock); + #endif /* __SDMMC_HOST_H__ */ diff --git a/firmware/export/sdmmc_poll.h b/firmware/export/sdmmc_poll.h new file mode 100644 index 0000000000..3323644e2f --- /dev/null +++ b/firmware/export/sdmmc_poll.h @@ -0,0 +1,62 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2025-2026 Aidan MacDonald + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#ifndef __SDMMC_POLL_H__ +#define __SDMMC_POLL_H__ + +#include "timeout.h" +#include + +struct sdmmc_host; +struct sdmmc_poll +{ + struct timeout timeout; + struct sdmmc_host *host; + + bool (*check_inserted)(void); + + bool is_inserted; + bool is_polling; + bool last_state; + bool curr_state; +}; + +/* + * Simple helper for polling for SD card insertion state. + * This calls sdmmc_host_set_medium_present() when a change + * in insertion state is detected via the check_inserted() + * callback. + * + * Call sdmmc_poll_start() to start the poll timer if you + * use level triggered polling. + * + * To use edge triggered events call sdmmc_poll_event() if + * the return value of check_inserted() may have changed. + * + * Changes in the insertion state are debounced before they + * are reported to the sdmmc_host. + */ + +void sdmmc_poll_init(struct sdmmc_poll *poll, + struct sdmmc_host *host, + bool (*check_inserted) (void)); +void sdmmc_poll_start(struct sdmmc_poll *poll); +void sdmmc_poll_event(struct sdmmc_poll *poll); + +#endif /* __SDMMC_POLL_H__ */ diff --git a/firmware/export/storage.h b/firmware/export/storage.h index 85c1ed9434..6cd52f9336 100644 --- a/firmware/export/storage.h +++ b/firmware/export/storage.h @@ -266,6 +266,9 @@ static inline void storage_sleep(void) {}; #define storage_removable(drive) nand_removable(IF_MD(drive)) #define storage_present(drive) nand_present(IF_MD(drive)) #endif + #ifdef HAVE_STORAGE_READONLY + #define storage_readonly(drive) nand_readonly(IF_MD(drive)) + #endif #define storage_driver_type(drive) (STORAGE_NAND_NUM) #elif (CONFIG_STORAGE & STORAGE_RAMDISK) #define STORAGE_FUNCTION(NAME) (ramdisk_## NAME) diff --git a/firmware/export/system.h b/firmware/export/system.h index 498c0b048d..cdfa948836 100644 --- a/firmware/export/system.h +++ b/firmware/export/system.h @@ -40,16 +40,6 @@ extern void system_init(void); extern long cpu_frequency; -struct flash_header { - uint32_t magic; - uint32_t length; - char version[32]; -}; - -bool detect_flashed_romimage(void); -bool detect_flashed_ramimage(void); -bool detect_original_firmware(void); - #if defined(HAVE_ADJUSTABLE_CPU_FREQ) \ && defined(ROCKBOX_HAS_LOGF) && (NUM_CORES == 1) #define CPU_BOOST_LOGGING diff --git a/firmware/export/usb-rk27xx.h b/firmware/export/usb-rk27xx.h new file mode 100644 index 0000000000..1d9a19518c --- /dev/null +++ b/firmware/export/usb-rk27xx.h @@ -0,0 +1,37 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2026 by Marcin Bukat + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#ifndef __USB_RK27XX_H__ +#define __USB_RK27XX_H__ + +#include +#include "cpu.h" /* USB_NUM_ENDPOINTS */ + +/* rk27xx UDC endpoint allocation: which endpoints may be allocated depends + * on what else is - interrupt and bulk endpoints never share a group of + * three - so the driver tracks the allocation itself (usb_drv.h, option 2; + * usb-drv-rk27xx.c). Include before usb_drv.h. */ +struct usb_drv_ep_alloc_ctx_rk27xx +{ + int8_t type[USB_NUM_ENDPOINTS][2]; + int max_packet_size[USB_NUM_ENDPOINTS][2]; +}; +#define usb_drv_ep_alloc_ctx usb_drv_ep_alloc_ctx_rk27xx + +#endif /* __USB_RK27XX_H__ */ diff --git a/firmware/export/usb.h b/firmware/export/usb.h index a324b1174c..de48c890f9 100644 --- a/firmware/export/usb.h +++ b/firmware/export/usb.h @@ -272,8 +272,20 @@ void usb_firewire_connect_event(void); void usb_set_hid(bool enable); #endif -#ifdef USB_ENABLE_AUDIO +#ifdef USB_ENABLE_SERIAL +void usb_set_serial(bool enable); +bool usb_get_serial(void); +#endif + +#if defined(USB_ENABLE_AUDIO) || defined(HAVE_HOST_USB_AUDIO) +/* Select when the USB Audio (DAC) function is active. Values follow the + * usb_audio setting: 0 never, 1 always, 2 while charge-only, 3 while + * mass-storage. Native targets use the usbstack driver; hosted targets + * (HiBy) drive their configfs gadget. */ void usb_set_audio(int value); + +/* true while host-PCM (USB-DAC) playback is active */ +bool usb_audio_get_active(void); #endif #if defined(USB_ENABLE_STORAGE) && defined(HAVE_MULTIDRIVE) diff --git a/firmware/export/usb_core.h b/firmware/export/usb_core.h index e995448fdd..60a7c80f3a 100644 --- a/firmware/export/usb_core.h +++ b/firmware/export/usb_core.h @@ -55,6 +55,9 @@ struct usb_class_driver; void usb_core_init(void); void usb_core_exit(void); +/* IRQ context, after both EP0 directions and their stale completions have + * been cancelled. Does not release a request buffer still owned by a handler. */ +void usb_core_control_cancelled(void); void usb_core_setup_received(struct usb_ctrlrequest* req); void usb_core_control_response(enum usb_control_response response, const void* data, size_t size); void usb_core_transfer_complete(int endpoint,int dir,int status,int length); @@ -67,7 +70,8 @@ void usb_core_handle_transfer_completion( #endif void usb_core_handle_notify(long id, intptr_t data); /* For controllers which handle SET ADDR and/or SET CONFIG in hardware */ -void usb_core_notify_set_address(uint8_t addr); +/* Address state only; hardware programming and status timing belong to drivers. */ +void usb_core_notify_set_address(uint8_t addr); /* IRQ-safe notification */ void usb_core_notify_set_config(uint8_t config); #ifdef HAVE_HOTSWAP diff --git a/firmware/export/usb_drv.h b/firmware/export/usb_drv.h index 4d197715e1..3c6b03adad 100644 --- a/firmware/export/usb_drv.h +++ b/firmware/export/usb_drv.h @@ -105,7 +105,12 @@ int usb_drv_send(int endpoint, void* ptr, int length); int usb_drv_send_nonblocking(int endpoint, void* ptr, int length); int usb_drv_recv_blocking(int endpoint, void* ptr, int length); int usb_drv_recv_nonblocking(int endpoint, void* ptr, int length); -void usb_drv_set_address(int address); +/* SET_ADDRESS is owned by the driver, including its status stage. */ +static inline bool usb_drv_is_set_address(const struct usb_ctrlrequest *req) +{ + return req->bRequestType == (USB_TYPE_STANDARD | USB_RECIP_DEVICE) && + req->bRequest == USB_REQ_SET_ADDRESS; +} void usb_drv_reset_endpoint(int endpoint, bool send); bool usb_drv_powered(void); int usb_drv_port_speed(void); diff --git a/firmware/export/videoout.h b/firmware/export/videoout.h new file mode 100644 index 0000000000..4dce7572e5 --- /dev/null +++ b/firmware/export/videoout.h @@ -0,0 +1,50 @@ +/*************************************************************************** + * Composite video output interface. + * + * Copyright (C) 2026 David Cormier + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + ****************************************************************************/ + +#ifndef VIDEOOUT_H +#define VIDEOOUT_H + +#include + +enum videoout_mode +{ + VIDEOOUT_OFF = 0, + VIDEOOUT_AUTO, + VIDEOOUT_ON, +}; + +enum videoout_accessory +{ + VIDEOOUT_ACCESSORY_NONE = 0, + VIDEOOUT_ACCESSORY_PENDING, + VIDEOOUT_ACCESSORY_VIDEO, + VIDEOOUT_ACCESSORY_OTHER, +}; + +bool videoout_disable(void); +bool videoout_active(void); +bool videoout_lcd_clock_required(void); +bool videoout_mirror_yuv420(const unsigned char *luma, + const unsigned char *cb, + const unsigned char *cr, + int source_x, int source_y, + int source_stride, + int x, int y, int width, int height); +void videoout_mirror_rgb565(const void *source, int x, int y, + int width, int height, int stride); +void videoout_set_mode(enum videoout_mode mode, const void *framebuffer, + int width, int height); + +/* IRQ-safe: requests, but does not perform, the sleeping ADC transaction. */ +void videoout_request_accessory_identification(void); +void videoout_accessory_state(enum videoout_accessory accessory); + +#endif /* VIDEOOUT_H */ diff --git a/firmware/export/wm8751.h b/firmware/export/wm8751.h index bae37a7a92..605a7437ea 100644 --- a/firmware/export/wm8751.h +++ b/firmware/export/wm8751.h @@ -183,6 +183,14 @@ AUDIOHW_SETTING(TREBLE_CUTOFF, "kHz", 0, 4, 4, 8, 4) #define ADDITIONAL1 0x17 #define ADDITIONAL1_TOEN (1 << 0) #define ADDITIONAL1_DACINV (1 << 1) +#if defined(HAVE_WM8750) +/* which ADC feeds the left and right channels of the output data */ +#define ADDITIONAL1_DATSEL_LR (0 << 2) +#define ADDITIONAL1_DATSEL_LL (1 << 2) +#define ADDITIONAL1_DATSEL_RR (2 << 2) +#define ADDITIONAL1_DATSEL_RL (3 << 2) +#define ADDITIONAL1_DATSEL_MASK (3 << 2) +#endif #define ADDITIONAL1_DMONOMIX_LLRR (0 << 4) #define ADDITIONAL1_DMONOMIX_ML0R (1 << 4) #define ADDITIONAL1_DMONOMIX_0LMR (2 << 4) diff --git a/firmware/export/wm8975.h b/firmware/export/wm8975.h index 8e5e109398..354f798033 100644 --- a/firmware/export/wm8975.h +++ b/firmware/export/wm8975.h @@ -313,4 +313,7 @@ void audiohw_enable_lineout(bool enable); #define WM8975_88200HZ 0x7f #define WM8975_96000HZ 0x5d +/* The WM1870 (HAVE_WM1870) answers these registers and a few above them */ +#define WM1870_R67 0x43 + #endif /* _WM8975_H */ diff --git a/firmware/export/wmcodec.h b/firmware/export/wmcodec.h index f57933eb51..d2938eb1be 100644 --- a/firmware/export/wmcodec.h +++ b/firmware/export/wmcodec.h @@ -20,3 +20,9 @@ void wmcodec_write(int reg, int data); +#ifdef HAVE_WM1870 +/* SAMPCTRL for a sample rate, which depends on the MCLK the target feeds + * the codec. Zero if the target cannot make that rate. */ +unsigned short wmcodec_sampctrl(unsigned long rate); +#endif + diff --git a/firmware/font.c b/firmware/font.c index 9ea194038a..f3d14c48f0 100644 --- a/firmware/font.c +++ b/firmware/font.c @@ -435,7 +435,7 @@ int font_load_ex( const char *path, size_t buf_size, int glyphs ) #endif /* load font struct f with file header */ - int file_size = filesize( fd ); + int file_size = ffilesize( fd ); struct font header; struct font f; @@ -1021,7 +1021,7 @@ static NO_INLINE void glyph_cache_load(const char *font_path, struct font *pf) if (hdr != FC_HEADER_VAL) goto latin; /* only read what fits */ - glyph_file_size = filesize( fd ); + glyph_file_size = ffilesize( fd ); if (glyph_file_size < sizeof(uint32_t)) goto latin; glyph_file_size -= sizeof(uint32_t); diff --git a/firmware/include/dir.h b/firmware/include/dir.h index 9cc32f522c..1619242cc3 100644 --- a/firmware/include/dir.h +++ b/firmware/include/dir.h @@ -27,8 +27,8 @@ #include "fs_attr.h" #include "fs_defines.h" -#if defined(CTRU) && !defined(SIMULATOR) -#include "filesystem-ctru.h" +#if (CONFIG_PLATFORM & PLATFORM_GAME_CONSOLE) && !defined(SIMULATOR) +#include "filesystem-console.h" #elif defined (APPLICATION) || defined(CHECKWPS) #include "filesystem-app.h" #elif defined(SIMULATOR) || defined(DBTOOL) @@ -65,6 +65,9 @@ #ifndef dir_exists #define dir_exists FS_PREFIX(dir_exists) #endif +#ifndef dir_get_info +#define dir_get_info FS_PREFIX(dir_get_info) +#endif #ifndef root_realpath #define root_realpath FS_PREFIX(root_realpath) #endif @@ -85,12 +88,4 @@ struct dirinfo time_t mtime; /* local file time */ }; -#ifndef DIRFUNCTIONS_DECLARED -/* TIP: set errno to zero before calling to see if anything failed */ -struct dirinfo dir_get_info(DIR *dirp, struct DIRENT *entry); -const char* root_realpath(void); -#endif /* !DIRFUNCTIONS_DECLARED */ - - - #endif /* _DIR_H_ */ diff --git a/firmware/include/dircache_redirect.h b/firmware/include/dircache_redirect.h index d7af8d78d9..329df8b3a6 100644 --- a/firmware/include/dircache_redirect.h +++ b/firmware/include/dircache_redirect.h @@ -192,7 +192,7 @@ static inline void volume_onmount_internal(IF_MV_NONVOID(int volume)) if (rtlen <= 0 || rtlen >= (int) sizeof(rtpath)) rtlen = 0; /* path too long or sprintf error */ - else if (file_exists(rtpath)) + else if (rbfs_file_exists(rtpath)) { rtlen = get_redirect_dir(rtpath, sizeof(rtpath), volume, "", ""); while (rtlen > 0 && rtpath[--rtlen] == PATH_SEPCH) diff --git a/firmware/include/file.h b/firmware/include/file.h index 72d8cbcafc..6a1b3795d0 100644 --- a/firmware/include/file.h +++ b/firmware/include/file.h @@ -39,8 +39,8 @@ enum relate_result RELATE_PREFIX, /* the path2 contains path1 as a prefix */ }; -#if defined(CTRU) && !defined(SIMULATOR) -#include "filesystem-ctru.h" +#if (CONFIG_PLATFORM & PLATFORM_GAME_CONSOLE) && !defined(SIMULATOR) +#include "filesystem-console.h" #elif defined(APPLICATION) || defined(CHECKWPS) #include "filesystem-app.h" #elif defined(SIMULATOR) || defined(DBTOOL) @@ -87,8 +87,8 @@ int fdprintf(int fildes, const char *fmt, ...) ATTRIBUTE_PRINTF(2, 3); #ifndef modtime #define modtime FS_PREFIX(modtime) #endif -#ifndef filesize -#define filesize FS_PREFIX(filesize) +#ifndef ffilesize +#define ffilesize FS_PREFIX(ffilesize) #endif #ifndef fsamefile #define fsamefile FS_PREFIX(fsamefile) diff --git a/firmware/include/filesystem-native.h b/firmware/include/filesystem-native.h index 0edce81ae3..aaced66429 100644 --- a/firmware/include/filesystem-native.h +++ b/firmware/include/filesystem-native.h @@ -27,7 +27,7 @@ #define DIRFUNCTIONS_DEFINED #endif /* PLUGIN || CODEC */ -#define FS_PREFIX(_x_) _x_ +#define FS_PREFIX(_x_) rbfs_ ## _x_ #endif /* _FILESYSTEM_NATIVE_H_ */ #ifdef _FILE_H_ @@ -44,26 +44,26 @@ #include -int open(const char *name, int oflag); -int creat(const char *name); -int close(int fildes); -int ftruncate(int fildes, off_t length); -int fsync(int fildes); -off_t lseek(int fildes, off_t offset, int whence); -ssize_t read(int fildes, void *buf, size_t nbyte); -ssize_t write(int fildes, const void *buf, size_t nbyte); -int remove(const char *path); -int rename(const char *old, const char *new); -int modtime(const char *path, time_t modtime); -off_t filesize(int fildes); -int fsamefile(int fildes1, int fildes2); -int relate(const char *path1, const char *path2); -bool file_exists(const char *path); +int rbfs_open(const char *name, int oflag); +int rbfs_creat(const char *name); +int rbfs_close(int fildes); +int rbfs_ftruncate(int fildes, off_t length); +int rbfs_fsync(int fildes); +off_t rbfs_lseek(int fildes, off_t offset, int whence); +ssize_t rbfs_read(int fildes, void *buf, size_t nbyte); +ssize_t rbfs_write(int fildes, const void *buf, size_t nbyte); +int rbfs_remove(const char *path); +int rbfs_rename(const char *old, const char *new); +int rbfs_modtime(const char *path, time_t modtime); +off_t rbfs_ffilesize(int fildes); +int rbfs_fsamefile(int fildes1, int fildes2); +int rbfs_relate(const char *path1, const char *path2); +bool rbfs_file_exists(const char *path); #endif /* !FILEFUNCTIONS_DECLARED */ #if !defined(RB_FILESYSTEM_OS) && !defined (FILEFUNCTIONS_DEFINED) -#define open(path, oflag, ...) open(path, oflag) -#define creat(path, mode) creat(path) +#define rbfs_open(path, oflag, ...) rbfs_open(path, oflag) +#define rbfs_creat(path, mode) rbfs_creat(path) #endif /* FILEFUNCTIONS_DEFINED */ #endif /* _FILESYSTEM_NATIVE__FILE_H_ */ @@ -93,16 +93,19 @@ typedef struct {} DIR; #define DIRFUNCTIONS_DEFINED #endif -DIR * opendir(const char *dirname); -struct dirent * readdir(DIR *dirp); -int readdir_r(DIR *dirp, struct dirent *entry, - struct dirent **result); -void rewinddir(DIR *dirp); -int closedir(DIR *dirp); -int mkdir(const char *path); -int rmdir(const char *path); -int samedir(DIR *dirp1, DIR *dirp2); -bool dir_exists(const char *dirname); +DIR * rbfs_opendir(const char *dirname); +struct dirent * rbfs_readdir(DIR *dirp); +int rbfs_readdir_r(DIR *dirp, struct dirent *entry, + struct dirent **result); +void rbfs_rewinddir(DIR *dirp); +int rbfs_closedir(DIR *dirp); +int rbfs_mkdir(const char *path); +int rbfs_rmdir(const char *path); +int rbfs_samedir(DIR *dirp1, DIR *dirp2); +bool rbfs_dir_exists(const char *dirname); + +struct dirinfo rbfs_dir_get_info(DIR *dirp, struct DIRENT *entry); +const char * rbfs_root_realpath(void); #endif /* !DIRFUNCTIONS_DECLARED */ #endif /* _FILESYSTEM_NATIVE__DIR_H_ */ diff --git a/firmware/include/rbendian.h b/firmware/include/rbendian.h index 039d3f9521..97bdcd80ab 100644 --- a/firmware/include/rbendian.h +++ b/firmware/include/rbendian.h @@ -27,7 +27,7 @@ #endif #ifndef __MINGW32__ -#if defined(__APPLE__) || defined(CTRU) +#if defined(__APPLE__) || defined(N3DS) #include #else #include diff --git a/firmware/kernel/include/thread.h b/firmware/kernel/include/thread.h index f23c4d9572..286fd97026 100644 --- a/firmware/kernel/include/thread.h +++ b/firmware/kernel/include/thread.h @@ -88,7 +88,7 @@ struct thread_entry; * * simulator (possibly) doesn't simulate stack usage anyway but well ... */ -#if defined(HAVE_SDL_THREADS) || defined(__PCTOOL__) || defined(CTRU) +#if defined(HAVE_SDL_THREADS) || defined(__PCTOOL__) || defined(N3DS) #define DEFAULT_STACK_SIZE 0x100 /* tiny, ignored anyway */ #else #include "asm/thread.h" diff --git a/firmware/kernel/include/timeout.h b/firmware/kernel/include/timeout.h index 1775000c52..140e9d5c9f 100644 --- a/firmware/kernel/include/timeout.h +++ b/firmware/kernel/include/timeout.h @@ -21,6 +21,7 @@ #define _KERNEL_H_ #include "config.h" +#include struct timeout; diff --git a/firmware/kernel/mutex.c b/firmware/kernel/mutex.c index 92e8594efe..8c8ddd224d 100644 --- a/firmware/kernel/mutex.c +++ b/firmware/kernel/mutex.c @@ -74,7 +74,7 @@ void mutex_lock(struct mutex *m) /* Release ownership of a mutex object - only owning thread must call this */ void mutex_unlock(struct mutex *m) { -#ifndef CTRU +#ifndef N3DS /* FIXME: synchronization primitives does not behave correctly between different cores */ /* unlocker not being the owner is an unlocking violation */ diff --git a/firmware/kernel/thread-common.c b/firmware/kernel/thread-common.c index 0170b66237..25c49069f9 100644 --- a/firmware/kernel/thread-common.c +++ b/firmware/kernel/thread-common.c @@ -230,7 +230,7 @@ void format_thread_name(char *buf, size_t bufsize, snprintf(buf, bufsize, fmt, name, thread->id); } -#if !defined(HAVE_SDL_THREADS) && !defined(CTRU) +#if !defined(HAVE_SDL_THREADS) && !(CONFIG_PLATFORM & PLATFORM_GAME_CONSOLE) /*--------------------------------------------------------------------------- * Returns the maximum percentage of the stack ever used during runtime. *--------------------------------------------------------------------------- @@ -251,7 +251,7 @@ static unsigned int stack_usage(uintptr_t *stackptr, size_t stack_size) return usage; } -#endif /* !defined(HAVE_SDL_THREADS) && !defined(CTRU) */ +#endif /* !defined(HAVE_SDL_THREADS) && !(CONFIG_PLATFORM & PLATFORM_GAME_CONSOLE) */ #if NUM_CORES > 1 int core_get_debug_info(unsigned int core, struct core_debug_info *infop) @@ -303,13 +303,16 @@ int thread_get_debug_info(unsigned int thread_id, #ifdef HAVE_SCHEDULER_BOOSTCTRL cpu_boost = thread->cpu_boost; #endif -#if !defined(HAVE_SDL_THREADS) && !defined(CTRU) +#if !defined(HAVE_SDL_THREADS) && !(CONFIG_PLATFORM & PLATFORM_GAME_CONSOLE) infop->stack_usage = stack_usage(thread->stack, thread->stack_size); size_t stack_used_current = thread->stack_size - (thread->context.sp - (uintptr_t)thread->stack); - infop->stack_usage_cur = stack_used_current * 100 / thread->stack_size; + /* Hosted implementations typically have the main stack managed + by the OS, so we create a fake entry with 0 length */ + infop->stack_usage_cur = thread->stack_size ? + stack_used_current * 100 / thread->stack_size : 0; #endif #if NUM_CORES > 1 infop->core = thread->core; diff --git a/firmware/kernel/thread-internal.h b/firmware/kernel/thread-internal.h index 427e6dd033..fcf9bb56d9 100644 --- a/firmware/kernel/thread-internal.h +++ b/firmware/kernel/thread-internal.h @@ -31,7 +31,7 @@ * * simulator (possibly) doesn't simulate stack usage anyway but well ... */ -#if defined(HAVE_SDL_THREADS) || defined(__PCTOOL__) || defined(CTRU) +#if defined(HAVE_SDL_THREADS) || defined(__PCTOOL__) || defined(N3DS) struct regs { void *t; /* OS thread */ diff --git a/firmware/lc-rock.c b/firmware/lc-rock.c index 73d743dfbd..23adb7f390 100644 --- a/firmware/lc-rock.c +++ b/firmware/lc-rock.c @@ -60,7 +60,7 @@ void * lc_open(const char *filename, unsigned char *buf, size_t buf_size) /* hdr.end_addr points to the end of the bss section, * but there might be idata/icode behind that so the bytes to copy * can be larger */ - copy_size = MAX(filesize(fd), hdr.end_addr - hdr.load_addr); + copy_size = MAX(ffilesize(fd), hdr.end_addr - hdr.load_addr); if (hdr.load_addr < buf || (hdr.load_addr+copy_size) > buf_end) { diff --git a/firmware/pcm.c b/firmware/pcm.c index 5fa368911f..bea1edb376 100644 --- a/firmware/pcm.c +++ b/firmware/pcm.c @@ -30,6 +30,7 @@ #include "general.h" #include "pcm-internal.h" #include "pcm_mixer.h" +#include "pcm_sw_volume.h" /** * Aspects implemented in the target-specific portion: @@ -105,12 +106,15 @@ struct pcm_sink* pcm_get_current_sink(void) return sinks[cur_sink]; } -#if !defined(HAVE_SW_VOLUME_CONTROL) || defined(PCM_SW_VOLUME_UNBUFFERED) /** Standard hw volume/unbuffered control functions - otherwise, see ** pcm_sw_volume.c **/ static inline void pcm_play_dma_start_int(const void *addr, size_t size) { -#ifdef HAVE_SW_VOLUME_CONTROL +#ifdef WANT_SWVOL + if (sinks[cur_sink]->caps.volume_type == PCM_SINK_SWVOL) { + pcm_play_dma_start_int_swvol(addr, size); + return; + } /* Smoothed transition might not have happened so sync now */ pcm_sync_pcm_factors(); #endif @@ -119,12 +123,23 @@ static inline void pcm_play_dma_start_int(const void *addr, size_t size) static inline void pcm_play_dma_stop_int(void) { +#ifdef WANT_SWVOL + if (sinks[cur_sink]->caps.volume_type == PCM_SINK_SWVOL) { + pcm_play_dma_stop_int_swvol(); + return; + } +#endif sinks[cur_sink]->ops.stop(); } bool pcm_play_dma_complete_callback(enum pcm_dma_status status, const void **addr, size_t *size) { +#ifdef WANT_SWVOL + if (sinks[cur_sink]->caps.volume_type == PCM_SINK_SWVOL) { + return pcm_play_dma_complete_callback_swvol(status, addr, size); + } +#endif /* Check status callback first if error */ if (status < PCM_DMAST_OK) status = pcm_play_dma_status_callback(status); @@ -137,7 +152,6 @@ bool pcm_play_dma_complete_callback(enum pcm_dma_status status, pcm_play_stop_int(); return false; } -#endif /* !HAVE_SW_VOLUME_CONTROL || PCM_SW_VOLUME_UNBUFFERED */ void pcm_play_stop_int(void) { diff --git a/firmware/pcm_mixer.c b/firmware/pcm_mixer.c index 5de15ac793..7c17255990 100644 --- a/firmware/pcm_mixer.c +++ b/firmware/pcm_mixer.c @@ -288,10 +288,11 @@ static void mixer_start_pcm(void) /* Notify users of samplerate change */ static void mixer_handle_sampr_change(unsigned int sampr) { - for (size_t i = 0; i < ARRAYLEN(active_channels) && active_channels[i]; i += 1) + int i = 0; + struct mixer_channel* chan; + while ((chan = active_channels[i])) { - struct mixer_channel* chan = active_channels[i]; - + bool stop_channel = true; /* Notify upstreams */ if (chan->play_cbs) { @@ -309,13 +310,19 @@ static void mixer_handle_sampr_change(unsigned int sampr) chan->start = start; chan->size = size; chan->last_size = 0; - } else { - channel_stopped(chan); + + stop_channel = false; + ++i; } } } + + if (stop_channel) + { + channel_stopped(chan); + } /* Notify buffer monitor */ - if (chan->buf_cbs) + else if (chan->buf_cbs) { if (chan->buf_cbs->sampr_changed) { diff --git a/firmware/pcm_sw_volume.c b/firmware/pcm_sw_volume.c index 99e5d62cb4..d4ed29f1e9 100644 --- a/firmware/pcm_sw_volume.c +++ b/firmware/pcm_sw_volume.c @@ -49,7 +49,7 @@ static uint32_t pcm_factor_l = 0, pcm_factor_r = 0; static typeof (memcpy) *pcm_scaling_fn = NULL; /* take care of some defines for 32-bit software vol */ -#if (PCM_NATIVE_BITDEPTH > 16) /* >16-bit */ +#ifdef WANT_SWVOL_32 /* >16-bit */ # define HAVE_SWVOL_32 # define PCM_VOL_SAMPLE_SIZE (2 * sizeof (int32_t)) # define PCM_DBL_BUF_SIZE_T int32_t @@ -210,7 +210,7 @@ void pcm_sync_pcm_factors(void) #endif } -#ifndef PCM_SW_VOLUME_UNBUFFERED +#ifdef WANT_SWVOL /* source buffer from client */ static const void * volatile src_buf_addr = NULL; static size_t volatile src_buf_rem = 0; @@ -227,7 +227,7 @@ static unsigned int frame_count, frame_err, frame_frac; /** Overrides of certain functions in pcm.c and pcm-internal.h **/ -bool pcm_play_dma_complete_callback(enum pcm_dma_status status, +bool pcm_play_dma_complete_callback_swvol(enum pcm_dma_status status, const void **addr, size_t *size) { /* Check status callback first if error */ @@ -268,7 +268,7 @@ static void update_frame_params(size_t size) /* Obtain the next buffer and prepare it for pcm driver playback */ enum pcm_dma_status -pcm_play_dma_status_callback_int(enum pcm_dma_status status) +pcm_play_dma_status_callback_int_swvol(enum pcm_dma_status status) { if (status != PCM_DMAST_STARTED) return status; @@ -327,7 +327,7 @@ static void start_pcm(bool reframe) pcm_get_current_sink()->ops.play(pcm_dbl_buf[1], pcm_dbl_buf_size[1]); } -void pcm_play_dma_start_int(const void *addr, size_t size) +void pcm_play_dma_start_int_swvol(const void *addr, size_t size) { src_buf_addr = addr; /* divide by 2 for 32 bit, optimize away to 1 for 16 bit */ @@ -335,14 +335,14 @@ void pcm_play_dma_start_int(const void *addr, size_t size) start_pcm(true); } -void pcm_play_dma_stop_int(void) +void pcm_play_dma_stop_int_swvol(void) { pcm_get_current_sink()->ops.stop(); src_buf_addr = NULL; src_buf_rem = 0; } -#endif /* PCM_SW_VOLUME_UNBUFFERED */ +#endif /* WANT_SWVOL */ /** Internal **/ diff --git a/firmware/reggen/stm32h743.regs b/firmware/reggen/stm32h743.regs index 03292c347c..8134b08f06 100644 --- a/firmware/reggen/stm32h743.regs +++ b/firmware/reggen/stm32h743.regs @@ -1757,3 +1757,283 @@ block ADC { ADC1 @ 0x40022000 : ADC ADC2 @ 0x40022100 : ADC ADC3 @ 0x58026000 : ADC + +block TIM { + CR1 @ 0x00 : reg { + -- 11 UIFREMAP + 09 08 CKD + -- 07 ARPE + 06 05 CMS + -- 04 DIR + -- 03 OPM + -- 02 URS + -- 01 UDIS + -- 00 CEN + } + + CR2 @ 0x04 : reg { + 23 20 MMS2 + -- 18 OIS6 + -- 16 OIS5 + -- 14 OIS4 + -- 13 OIS3N + -- 12 OIS3 + -- 11 OIS2N + -- 10 OIS2 + -- 09 OIS1N + -- 08 OIS1 + -- 07 TI1S + 06 04 MMS + -- 03 CCDS + -- 02 CCUS + -- 00 CCPC + } + + SMCR @ 0x08 : reg { + 21 20 TS_4_3 + -- 16 SMS3 + -- 15 ETP + -- 14 ECE + 13 12 ETPS + 11 08 ETF + -- 07 MSM + 06 04 TS + 02 00 SMS + } + + DIER @ 0x0c : reg { + -- 14 TDE + -- 13 COMDE + -- 12 CC4DE + -- 11 CC3DE + -- 10 CC2DE + -- 09 CC1DE + -- 08 UDE + -- 07 BIE + -- 06 TIE + -- 05 COMIE + -- 04 CC4IE + -- 03 CC3IE + -- 02 CC2IE + -- 01 CC1IE + -- 00 UIE + } + + SR @ 0x10 : reg { + -- 17 CC6IF + -- 16 CC5IF + -- 13 SBIF + -- 12 CC4OF + -- 11 CC3OF + -- 10 CC2OF + -- 09 CC1OF + -- 08 B2IF + -- 07 BIF + -- 06 TIF + -- 05 COMIF + -- 04 CC4IF + -- 03 CC3IF + -- 02 CC2IF + -- 01 CC1IF + -- 00 UIF + } + + EGR @ 0x14 : reg { + -- 08 B2G + -- 07 BG + -- 06 TG + -- 05 COMG + -- 04 CC4G + -- 03 CC3G + -- 02 CC2G + -- 01 CC1G + -- 00 UG + } + + CCMR1 @ 0x18 : reg { + 09 08 CC2S + 01 00 CC1S + } + + CCMR1_IN @ 0x18 : reg { + 15 12 IC2F + 11 10 IC2PSC + 09 08 CC2S + 07 04 IC1F + 03 02 IC1PSC + 01 00 CC1S + } + + CCMR1_OUT @ 0x18 : reg { + -- 24 OC2M3 + -- 16 OC1M3 + -- 15 OC2CE + 14 12 OC2M + -- 11 OC2PE + -- 10 OC2FE + 09 08 CC2S + -- 07 OC1CE + 06 04 OC1M + -- 03 OC1PE + -- 02 OC1FE + 01 00 CC1S + } + + CCMR2 @ 0x1c : reg { + 09 08 CC4S + 01 00 CC3S + } + + CCMR2_IN @ 0x1c : reg { + 15 12 IC4F + 11 10 IC4PSC + 09 08 CC4S + 07 04 IC3F + 03 02 IC3PSC + 01 00 CC3S + } + + CCMR2_OUT @ 0x1c : reg { + -- 24 OC4M3 + -- 16 OC3M3 + -- 15 OC4CE + 14 12 OC4M + -- 11 OC4PE + -- 10 OC4FE + 09 08 CC4S + -- 07 OC3CE + 06 04 OC3M + -- 03 OC3PE + -- 02 OC3FE + 01 00 CC3S + } + + CCER @ 0x20 : reg { + -- 21 CC6P + -- 20 CC6E + -- 17 CC5P + -- 16 CC5E + -- 15 CC4NP + -- 14 CC4NE + -- 13 CC4P + -- 12 CC4E + -- 11 CC3NP + -- 10 CC3NE + -- 09 CC3P + -- 08 CC3E + -- 07 CC2NP + -- 06 CC2NE + -- 05 CC2P + -- 04 CC2E + -- 03 CC1NP + -- 02 CC1NE + -- 01 CC1P + -- 00 CC1E + } + + CNT @ 0x24 : reg { + -- 31 UIFCPY + 15 00 CNT + } + + CNT32 @ 0x24 : reg { + -- 31 UIFCPY + 30 00 CNT + } + + PSC @ 0x28 : reg + ARR @ 0x2c : reg + RCR @ 0x30 : reg + + CCR1 @ 0x34 : reg + CCR2 @ 0x38 : reg + CCR3 @ 0x3c : reg + CCR4 @ 0x40 : reg + + BDTR @ 0x44 : reg { + -- 25 BK2P + -- 24 BK2E + 23 20 BK2F + 19 16 BKF + -- 15 MOE + -- 14 AOE + -- 13 BKP + -- 12 BKE + -- 11 OSSR + -- 10 OSSI + 09 08 LOCK + 07 00 DTG + } + + DCR @ 0x48 : reg { + 12 08 DBL + 04 00 DBA + } + + DMAR @ 0x4c : reg + + CCMR3_OUT @ 0x54 : reg { + -- 24 OC6M3 + -- 16 OC5M3 + -- 15 OC6CE + 14 12 OC6M + -- 11 OC6PE + -- 10 OC6FE + -- 07 OC5CE + 06 04 OC5M + -- 03 OC5PE + -- 02 OC5FE + } + + CCR5 @ 0x58 : reg { + -- 31 GC5C3 + -- 30 GC5C2 + -- 29 GC5C1 + 15 00 CCR5 + } + + CCR6 @ 0x5c : reg + + AF1 @ 0x60 : reg { + 17 14 ETRSEL + -- 11 BKCMP2P + -- 10 BKCMP1P + -- 09 BKINP + -- 08 BKDFxBKxE // differs for TIM1/TIM8 + -- 02 BKCMP2E + -- 01 BKCMP1E + -- 00 BKINE + } + + AF2 @ 0x64 : reg { + -- 11 BK2CMP2P + -- 10 BK2CMP1P + -- 09 BK2INP + -- 08 BK2DFxBKxE // differs for TIM1/TIM8 + -- 02 BK2CMP2E + -- 01 BK2CMP1E + -- 00 BK2INE + } + + TISEL @ 0x68 : reg { + 27 24 TI4SEL + 19 16 TI3SEL + 11 08 TI2SEL + 03 00 TI1SEL + } +} + +TIM17 @ 0x40014800 : TIM +TIM16 @ 0x40014400 : TIM +TIM15 @ 0x40014000 : TIM +TIM8 @ 0x40010400 : TIM +TIM1 @ 0x40010000 : TIM +TIM14 @ 0x40002000 : TIM +TIM13 @ 0x40001c00 : TIM +TIM12 @ 0x40001800 : TIM +TIM7 @ 0x40001400 : TIM +TIM6 @ 0x40001000 : TIM +TIM5 @ 0x40000c00 : TIM +TIM4 @ 0x40000800 : TIM +TIM3 @ 0x40000400 : TIM +TIM2 @ 0x40000000 : TIM diff --git a/firmware/rolo.c b/firmware/rolo.c index d832496270..0210cfe78c 100644 --- a/firmware/rolo.c +++ b/firmware/rolo.c @@ -28,7 +28,6 @@ #include "kernel.h" #include "button.h" #include "file.h" -#include "audio.h" #include "system.h" #include "i2c.h" #include "adc.h" @@ -219,6 +218,7 @@ extern unsigned long loadaddress; * Name: rolo_load(const char *filename) * Filename must be a fully defined filename including the path and extension * + * Must call audio_hard_stop() before calling us! ***************************************************************************/ int rolo_load(const char* filename) { @@ -238,8 +238,6 @@ int rolo_load(const char* filename) lcd_remote_update(); #endif - audio_hard_stop(); - /* get the system buffer. release only in case of error, otherwise * we don't return anyway */ rolo_handle = core_alloc_maximum(&filebuf_size, &buflib_ops_locked); diff --git a/firmware/sound.c b/firmware/sound.c index 40975abfc6..c7d510dc2f 100644 --- a/firmware/sound.c +++ b/firmware/sound.c @@ -196,7 +196,6 @@ int sound_current(int setting) } /* switch(setting) */ }/* sound_current */ -#if !defined(AUDIOHW_HAVE_CLIPPING) /* * The prescaler compensates for any kind of boosts, to prevent clipping. * @@ -304,7 +303,6 @@ static void set_prescaled_volume(void) audiohw_set_lineout_volume(0, 0); #endif /* AUDIOHW_HAVE_LINEOUT */ } -#endif /* AUDIOIHW_HAVE_CLIPPING */ void sound_set_volume(int value) { @@ -325,12 +323,8 @@ void sound_set_volume(int value) global_status.volume = value; #endif -#if defined(AUDIOHW_HAVE_CLIPPING) - audiohw_set_volume(value); -#else sound_prescaler.volume = sound_value_to_cb(SOUND_VOLUME, value); set_prescaled_volume(); -#endif } void sound_set_balance(int value) @@ -338,12 +332,8 @@ void sound_set_balance(int value) if (!audio_is_initialized) return; -#if defined(AUDIOHW_HAVE_BALANCE) - audiohw_set_balance(value); -#else sound_prescaler.balance = value; set_prescaled_volume(); -#endif } #if defined(AUDIOHW_HAVE_BASS) @@ -354,10 +344,8 @@ void sound_set_bass(int value) audiohw_set_bass(value); -#if !defined(AUDIOHW_HAVE_CLIPPING) sound_prescaler.bass = sound_value_to_cb(SOUND_BASS, value); set_prescaled_volume(); -#endif } #endif /* AUDIOHW_HAVE_BASS */ @@ -369,10 +357,8 @@ void sound_set_treble(int value) audiohw_set_treble(value); -#if !defined(AUDIOHW_HAVE_CLIPPING) sound_prescaler.treble = sound_value_to_cb(SOUND_TREBLE, value); set_prescaled_volume(); -#endif } #endif /* AUDIOHW_HAVE_TREBLE */ @@ -517,11 +503,9 @@ static void sound_set_hw_eq_band_gain(unsigned int band, int value) audiohw_set_eq_band_gain(band, value); -#if !defined (AUDIOHW_HAVE_CLIPPING) int setting = sound_enum_hw_eq_band_setting(band, AUDIOHW_EQ_GAIN); sound_prescaler.eq_gain[band] = sound_value_to_cb(setting, value); set_prescaled_volume(); -#endif /* AUDIOHW_HAVE_CLIPPING */ } void sound_set_hw_eq_band1_gain(int value) diff --git a/firmware/system.c b/firmware/system.c index 20c927efeb..d709bb1a02 100644 --- a/firmware/system.c +++ b/firmware/system.c @@ -161,43 +161,3 @@ void cpu_idle_mode(bool on_off) cpu_boost_unlock(); } #endif /* HAVE_ADJUSTABLE_CPU_FREQ */ - - -#ifdef HAVE_FLASHED_ROCKBOX -static bool detect_flash_header(uint8_t *addr) -{ -#ifndef BOOTLOADER - int oldmode = system_memory_guard(MEMGUARD_NONE); -#endif - struct flash_header hdr; - memcpy(&hdr, addr, sizeof(struct flash_header)); -#ifndef BOOTLOADER - system_memory_guard(oldmode); -#endif - return hdr.magic == FLASH_MAGIC; -} -#endif - -bool detect_flashed_romimage(void) -{ -#ifdef HAVE_FLASHED_ROCKBOX - return detect_flash_header((uint8_t *)FLASH_ROMIMAGE_ENTRY); -#else - return false; -#endif /* HAVE_FLASHED_ROCKBOX */ -} - -bool detect_flashed_ramimage(void) -{ -#ifdef HAVE_FLASHED_ROCKBOX - return detect_flash_header((uint8_t *)FLASH_RAMIMAGE_ENTRY); -#else - return false; -#endif /* HAVE_FLASHED_ROCKBOX */ -} - -bool detect_original_firmware(void) -{ - return !(detect_flashed_ramimage() || detect_flashed_romimage()); -} - diff --git a/firmware/target/arm/as3525/boot.lds b/firmware/target/arm/as3525/boot.lds index 41737f5112..ac6c25e11f 100644 --- a/firmware/target/arm/as3525/boot.lds +++ b/firmware/target/arm/as3525/boot.lds @@ -7,7 +7,7 @@ OUTPUT_FORMAT(elf32-littlearm) OUTPUT_ARCH(arm) STARTUP(target/arm/crt0.o) -#define LOAD_SIZE 0x100000 /* Reserve 1MB for loading the firmware */ +#define LOAD_SIZE 0x200000 /* Reserve 2MB for loading the firmware */ MEMORY { diff --git a/firmware/target/arm/as3525/pcm-as3525.c b/firmware/target/arm/as3525/pcm-as3525.c index 029c075e52..dbc362910f 100644 --- a/firmware/target/arm/as3525/pcm-as3525.c +++ b/firmware/target/arm/as3525/pcm-as3525.c @@ -205,6 +205,7 @@ struct pcm_sink builtin_pcm_sink = { .samprs = hw_freq_sampr, .num_samprs = HW_NUM_FREQ, .default_freq = HW_FREQ_DEFAULT, + .volume_type = PCM_NATIVE_VOLUME_TYPE, }, .ops = { .init = sink_dma_init, diff --git a/firmware/target/arm/as3525/system-as3525.c b/firmware/target/arm/as3525/system-as3525.c index aced3e2b80..c57544d7aa 100644 --- a/firmware/target/arm/as3525/system-as3525.c +++ b/firmware/target/arm/as3525/system-as3525.c @@ -398,12 +398,6 @@ void system_exception_wait(void) while(!button_read_device()); } -int system_memory_guard(int newmode) -{ - (void)newmode; - return 0; -} - /* usecs may be at most 2^32/248 (17 seconds) for 248MHz max cpu freq */ void udelay(unsigned usecs) { diff --git a/firmware/target/arm/as3525/usb-drv-as3525.c b/firmware/target/arm/as3525/usb-drv-as3525.c index ba0b485f1b..88e501ecb1 100644 --- a/firmware/target/arm/as3525/usb-drv-as3525.c +++ b/firmware/target/arm/as3525/usb-drv-as3525.c @@ -58,7 +58,19 @@ static struct usb_dev_dma_desc dmadescs[USB_NUM_EPS][2] __attribute__((aligned(3 /* reuse unused EP2 OUT descriptor here */ static struct usb_dev_setup_buf *setup_desc = (void*)&dmadescs[2][1]; +static bool address_status; +static bool handle_set_address(struct usb_ctrlrequest *req); + +/* SET_ADDRESS status belongs to this driver, not the core EP0 machine. */ +static void complete_transfer(int ep, int dir, int status, int length) +{ + if(ep == EP_CONTROL && address_status) + return; + usb_core_transfer_complete(ep, dir, status, length); +} + #if AS3525_MCLK_SEL != AS3525_CLK_PLLB + static inline void usb_enable_pll(void) { CGU_COUNTB = CGU_LOCK_CNT; @@ -181,7 +193,7 @@ static void reset_endpoints(int init) endpoints[i][0].rc = -1; semaphore_release(&endpoints[i][0].complete); } else { - usb_core_transfer_complete(i, USB_DIR_IN, -1, 0); + complete_transfer(i, USB_DIR_IN, -1, 0); } } endpoints[i][0].state = 0; @@ -189,7 +201,7 @@ static void reset_endpoints(int init) if (i != 2) { /* Skip the OUT EP0 alias */ if (endpoints[i][1].state & EP_STATE_BUSY) - usb_core_transfer_complete(i, USB_DIR_OUT, -1, 0); + complete_transfer(i, USB_DIR_OUT, -1, 0); endpoints[i][1].state = 0; semaphore_wait(&endpoints[i][1].complete, TIMEOUT_NOBLOCK); USB_OEP_SUP_PTR(i) = 0; @@ -555,7 +567,7 @@ static void handle_in_ep(int ep) endpoints[ep][0].uc_desc->status); if (endpoints[ep][0].state & EP_STATE_ASYNC) { endpoints[ep][0].state &= ~EP_STATE_ASYNC; - usb_core_transfer_complete(ep, USB_DIR_IN, 0, endpoints[ep][0].len); + complete_transfer(ep, USB_DIR_IN, 0, endpoints[ep][0].len); } else { semaphore_release(&endpoints[ep][0].complete); } @@ -606,7 +618,7 @@ static void handle_out_ep(int ep) if (endpoints[ep][1].state & EP_STATE_BUSY) { endpoints[ep][1].state &= ~EP_STATE_BUSY; endpoints[ep][1].rc = 0; - usb_core_transfer_complete(ep, USB_DIR_OUT, 0, dma_len); + complete_transfer(ep, USB_DIR_OUT, 0, dma_len); } else { logf("EP%d OUT, but no one was listening?\n", ep); } @@ -628,7 +640,8 @@ static void handle_out_ep(int ep) req->wIndex, req->wLength); - usb_core_setup_received(&req_copy); + if(!handle_set_address(&req_copy)) + usb_core_setup_received(&req_copy); setup_desc_init(setup_desc); ep_sts &= ~USB_EP_STAT_SETUP_RCVD; @@ -733,7 +746,8 @@ void INT_USB_FUNC(void) got_set_configuration = 1; set_config.wValue = USB_DEV_STS & USB_DEV_STS_MASK_CFG; - usb_core_setup_received(&set_config); + if(!handle_set_address(&set_config)) + usb_core_setup_received(&set_config); intr &= ~USB_DEV_INTR_SET_CONFIG; } if (intr & USB_DEV_INTR_EARLY_SUSPEND) {/* idle >3ms detected */ @@ -743,7 +757,8 @@ void INT_USB_FUNC(void) if (intr & USB_DEV_INTR_USB_RESET) {/* usb reset from host? */ logf("usb reset\n"); reset_endpoints(1); - usb_core_bus_reset(); + address_status = false; + usb_core_bus_reset(); intr &= ~USB_DEV_INTR_USB_RESET; } if (intr & USB_DEV_INTR_USB_SUSPEND) {/* suspend req from host? */ @@ -791,10 +806,7 @@ void usb_drv_set_test_mode(int mode) } /* handled internally by controller */ -void usb_drv_set_address(int address) -{ - (void)address; -} + void usb_drv_stall(int ep, bool stall, bool in) { @@ -806,3 +818,17 @@ bool usb_drv_stalled(int ep, bool in) { return USB_EP_CTRL(ep, in) & USB_EP_CTRL_STALL; } + +static bool handle_set_address(struct usb_ctrlrequest *req) +{ + address_status = false; + if(!usb_drv_is_set_address(req)) + return false; + + const uint8_t address = req->wValue & 0x7f; + address_status = true; + usb_drv_cancel_all_transfers(); + usb_drv_send_nonblocking(EP_CONTROL, NULL, 0); + usb_core_notify_set_address(address); + return true; +} diff --git a/firmware/target/arm/imx233/boot.lds b/firmware/target/arm/imx233/boot.lds index 826bed392e..e21711785c 100644 --- a/firmware/target/arm/imx233/boot.lds +++ b/firmware/target/arm/imx233/boot.lds @@ -7,7 +7,7 @@ OUTPUT_ARCH(arm) STARTUP(target/arm/imx233/crt0.o) /* Leave a hole at the beginning of the RAM to load the firmware */ -#define RAM_HOLE 1536 * 1024 +#define RAM_HOLE 2048 * 1024 /* Make a difference between virtual and physical address so that we can use * the resulting elf file with the elftosb tools which loads at the *physical* diff --git a/firmware/target/arm/imx233/pcm-imx233.c b/firmware/target/arm/imx233/pcm-imx233.c index 0c5b4624f4..7ee5860c0f 100644 --- a/firmware/target/arm/imx233/pcm-imx233.c +++ b/firmware/target/arm/imx233/pcm-imx233.c @@ -175,6 +175,7 @@ struct pcm_sink builtin_pcm_sink = { .samprs = hw_freq_sampr, .num_samprs = HW_NUM_FREQ, .default_freq = HW_FREQ_DEFAULT, + .volume_type = PCM_NATIVE_VOLUME_TYPE, }, .ops = { .init = audiohw_preinit, diff --git a/firmware/target/arm/imx233/system-imx233.c b/firmware/target/arm/imx233/system-imx233.c index 0bb62639d0..3b79aa079c 100644 --- a/firmware/target/arm/imx233/system-imx233.c +++ b/firmware/target/arm/imx233/system-imx233.c @@ -154,12 +154,6 @@ void system_exception_wait(void) while(imx233_power_read_pswitch() != 0) {} } -int system_memory_guard(int newmode) -{ - (void)newmode; - return 0; -} - static void set_page_tables(void) { /* map every memory region to itself */ diff --git a/firmware/target/arm/imx31/gigabeat-s/pcm-gigabeat-s.c b/firmware/target/arm/imx31/gigabeat-s/pcm-gigabeat-s.c index 25d149f74d..772964aaae 100644 --- a/firmware/target/arm/imx31/gigabeat-s/pcm-gigabeat-s.c +++ b/firmware/target/arm/imx31/gigabeat-s/pcm-gigabeat-s.c @@ -229,6 +229,7 @@ struct pcm_sink builtin_pcm_sink = { .samprs = hw_freq_sampr, .num_samprs = HW_NUM_FREQ, .default_freq = HW_FREQ_DEFAULT, + .volume_type = PCM_NATIVE_VOLUME_TYPE, }, .ops = { .init = sink_dma_init, diff --git a/firmware/target/arm/imx31/gigabeat-s/system-gigabeat-s.c b/firmware/target/arm/imx31/gigabeat-s/system-gigabeat-s.c index 4ae9439134..e3a4c722d0 100644 --- a/firmware/target/arm/imx31/gigabeat-s/system-gigabeat-s.c +++ b/firmware/target/arm/imx31/gigabeat-s/system-gigabeat-s.c @@ -174,12 +174,6 @@ void gpt_stop(void) restore_interrupt(status); } -int system_memory_guard(int newmode) -{ - (void)newmode; - return 0; -} - void system_halt(void) { disable_interrupt(IRQ_FIQ_STATUS); diff --git a/firmware/target/arm/ipod/button-clickwheel.c b/firmware/target/arm/ipod/button-clickwheel.c index 4c54e556cd..1f4ca13dc8 100644 --- a/firmware/target/arm/ipod/button-clickwheel.c +++ b/firmware/target/arm/ipod/button-clickwheel.c @@ -45,6 +45,9 @@ #include "pmu-target.h" #include "clocking-s5l8702.h" #endif +#ifdef HAVE_MIKEY_REMOTE +#include "mikey-target.h" /* inline earphone remote */ +#endif #define WHEEL_FAST_OFF_TIMEOUT 250000 /* timeout for acceleration = 250ms */ #define WHEEL_REPEAT_TIMEOUT 250000 /* timeout for button repeat = 250ms */ @@ -399,6 +402,9 @@ void button_init_device(void) #endif s5l_clickwheel_init(); semaphore_wait(&button_init_wakeup, HZ / 10); +#ifdef HAVE_MIKEY_REMOTE + mikey_init(); /* start inline earphone remote polling */ +#endif } bool button_hold(void) @@ -419,8 +425,13 @@ bool headphones_inserted(void) #if CONFIG_CPU==S5L8701 return ((PDAT14 & (1 << 5)) != 0); #elif CONFIG_CPU==S5L8702 || CONFIG_CPU==S5L8720 +#ifdef IPOD_NANO3G + /* GPIO 7.0, high with headphones in (measured) */ + return (PDAT(7) & (1 << 0)) != 0; +#else return ((PDAT10 & (1 << 6)) != 0); #endif +#endif } #endif @@ -474,9 +485,14 @@ int button_read_device(void) } /* The int_btn variable is set in the button interrupt handler */ + int btn = int_btn; #ifdef IPOD_ACCESSORY_PROTOCOL - return int_btn | remote_control_rx(); -#else - return int_btn; + btn |= remote_control_rx(); #endif +#ifdef HAVE_MIKEY_REMOTE + /* not gated by the hold switch, matching the OF: the inline remote + * keeps working while the device is locked */ + btn |= mikey_button_read(); +#endif + return btn; } diff --git a/firmware/target/arm/pcm-telechips.c b/firmware/target/arm/pcm-telechips.c index 3c9ca6b9b5..37c69a5323 100644 --- a/firmware/target/arm/pcm-telechips.c +++ b/firmware/target/arm/pcm-telechips.c @@ -174,6 +174,7 @@ struct pcm_sink builtin_pcm_sink = { .samprs = hw_freq_sampr, .num_samprs = HW_NUM_FREQ, .default_freq = HW_FREQ_DEFAULT, + .volume_type = PCM_NATIVE_VOLUME_TYPE, }, .ops = { .init = sink_dma_init, diff --git a/firmware/target/arm/pp/ata-target.h b/firmware/target/arm/pp/ata-target.h index fb92900616..da0ba3adcf 100644 --- a/firmware/target/arm/pp/ata-target.h +++ b/firmware/target/arm/pp/ata-target.h @@ -68,9 +68,9 @@ * UDMA 2 is stable at 30 Mhz. * UDMA 1 is stable at 24 Mhz. * - * A slower mode is used on iPod 4Gs due to reported instabilities. + * A slower mode is used on iPod 4G & color due to reported instabilities. */ -#if CPUFREQ_NORMAL >= 30000000 && !defined(IPOD_4G) +#if CPUFREQ_NORMAL >= 30000000 && !defined(IPOD_4G) && !defined(IPOD_COLOR) #define ATA_MAX_UDMA 2 #elif CPUFREQ_NORMAL >= 24000000 #define ATA_MAX_UDMA 1 diff --git a/firmware/target/arm/pp/pcm-pp.c b/firmware/target/arm/pp/pcm-pp.c index 2a1ddd7668..08b7328e1c 100644 --- a/firmware/target/arm/pp/pcm-pp.c +++ b/firmware/target/arm/pp/pcm-pp.c @@ -517,6 +517,7 @@ struct pcm_sink builtin_pcm_sink = { .samprs = hw_freq_sampr, .num_samprs = HW_NUM_FREQ, .default_freq = HW_FREQ_DEFAULT, + .volume_type = PCM_NATIVE_VOLUME_TYPE, }, .ops = { .init = sink_dma_init, diff --git a/firmware/target/arm/pp/system-pp5002.c b/firmware/target/arm/pp/system-pp5002.c index 75f99674d7..1cf284c294 100644 --- a/firmware/target/arm/pp/system-pp5002.c +++ b/firmware/target/arm/pp/system-pp5002.c @@ -235,9 +235,3 @@ void system_exception_wait(void) sleep_core(CURRENT_CORE); while (1); } - -int system_memory_guard(int newmode) -{ - (void)newmode; - return 0; -} diff --git a/firmware/target/arm/pp/system-pp502x.c b/firmware/target/arm/pp/system-pp502x.c index e8834061e1..ef69a459ec 100644 --- a/firmware/target/arm/pp/system-pp502x.c +++ b/firmware/target/arm/pp/system-pp502x.c @@ -664,9 +664,3 @@ void system_exception_wait(void) PROC_CTL(CURRENT_CORE) = 0x40000000; while (1); } - -int system_memory_guard(int newmode) -{ - (void)newmode; - return 0; -} diff --git a/firmware/target/arm/rk27xx/ata-nand-rk27xx.c b/firmware/target/arm/rk27xx/ata-nand-rk27xx.c index ea493cd661..d8a3fbba12 100644 --- a/firmware/target/arm/rk27xx/ata-nand-rk27xx.c +++ b/firmware/target/arm/rk27xx/ata-nand-rk27xx.c @@ -28,26 +28,29 @@ #include "ftl-target.h" #include "nand-target.h" -uint32_t ftl_banks; -const struct nand_device_info_type* ftl_nand_type; -/* This file provides only STUBS for now */ - /** static, private data **/ static bool initialized = false; +/* The NAND is FTL_NUM_DRIVES drives - USER, and SYS when the target exposes + * it. storage.c hands us a drive index relative to our first, which is the + * FTL_DRIVE_* number. */ +#ifdef HAVE_MULTIDRIVE +#define NAND_DRIVE(d) (d) +#else +#define NAND_DRIVE(d) FTL_DRIVE_USER +#endif + /* API Functions */ int nand_read_sectors(IF_MD(int drive,) sector_t start, int incount, void* inbuf) { - (void)drive; - return ftl_read(start, incount, inbuf); + return ftl_read(NAND_DRIVE(IF_MD_DRV(drive)), start, incount, inbuf); } int nand_write_sectors(IF_MD(int drive,) sector_t start, int count, const void* outbuf) { - (void)drive; - return ftl_write(start, count, outbuf); + return ftl_write(NAND_DRIVE(IF_MD_DRV(drive)), start, count, outbuf); } void nand_spindown(int seconds) @@ -72,13 +75,16 @@ void nand_enable(bool on) void nand_get_info(IF_MD(int drive,) struct storage_info *info) { - (void)drive; - uint32_t ppb = ftl_banks * (*ftl_nand_type).pagesperblock; + int d = NAND_DRIVE(IF_MD_DRV(drive)); + + /* Capacity comes from the FTL's own tables, not from raw block + * geometry: Scheme A's usable size depends on the per-zone valid-block + * counts and on the system/user split recorded in ID block 1. */ (*info).sector_size = SECTOR_SIZE; - (*info).num_sectors = (*ftl_nand_type).userblocks * ppb; - (*info).vendor = ""; - (*info).product = ""; - (*info).revision = ""; + (*info).num_sectors = ftl_get_sectors(d); + (*info).vendor = "Rockchip"; + (*info).product = ftl_drive_is_sys(d) ? "NAND SYS" : "NAND USER"; + (*info).revision = "1.0"; } long nand_last_disk_activity(void) @@ -109,7 +115,11 @@ int nand_num_drives(int first_drive) /* We don't care which logical drive number(s) we have been assigned */ (void)first_drive; +#ifdef HAVE_MULTIDRIVE + return FTL_NUM_DRIVES; +#else return 1; +#endif } #endif diff --git a/firmware/target/arm/rk27xx/audio-rk27xx.c b/firmware/target/arm/rk27xx/audio-rk27xx.c index 6307274af2..231b87d883 100644 --- a/firmware/target/arm/rk27xx/audio-rk27xx.c +++ b/firmware/target/arm/rk27xx/audio-rk27xx.c @@ -29,10 +29,15 @@ static int output_source = AUDIO_SRC_PLAYBACK; static void select_audio_path(void) { +#ifdef HAVE_RECORDING + /* the codec routes the source for listening and recording both */ + audiohw_set_recsrc(input_source, input_flags & SRCF_RECORDING); +#else if(input_source == AUDIO_SRC_PLAYBACK) audiohw_set_monitor(false); else audiohw_set_monitor(true); +#endif } void audio_input_mux(int source, unsigned flags) diff --git a/firmware/target/arm/rk27xx/backlight-rk27xx.c b/firmware/target/arm/rk27xx/backlight-rk27xx.c index 6b7459a69e..f658f5f920 100644 --- a/firmware/target/arm/rk27xx/backlight-rk27xx.c +++ b/firmware/target/arm/rk27xx/backlight-rk27xx.c @@ -39,7 +39,7 @@ static int brightness = DEFAULT_BRIGHTNESS_SETTING; * So the function used to calculate the values in the matrix was: * f(x) = (PWM_FREQ_MAX - PWM_FREQ_MIN) * ((100*x/31 + 16)/116)^3 + PWM_FREQ_MIN */ -#ifdef RK27_GENERIC +#if defined(RK27_GENERIC) || defined(SAMSUNG_YPCP3) static const unsigned short lin_brightness[] = { 8110, 8191, 8304, 8455, 8649, 8892, 9189, 9545, 9966, 10457, 11024, 11671, 12406, 13232, 14156, 15182, diff --git a/firmware/target/arm/rk27xx/debug-rk27xx.c b/firmware/target/arm/rk27xx/debug-rk27xx.c index 28fd96da78..1204102f83 100644 --- a/firmware/target/arm/rk27xx/debug-rk27xx.c +++ b/firmware/target/arm/rk27xx/debug-rk27xx.c @@ -29,8 +29,10 @@ #include "storage.h" #include "lcd-target.h" -#ifdef RK27_GENERIC +#if defined(RK27_GENERIC) #define DEBUG_CANCEL BUTTON_VOL +#elif defined(SAMSUNG_YPCP3) +#define DEBUG_CANCEL BUTTON_BACK #elif defined(HM60X) || defined(HM801) || (CONFIG_KEYPAD == MA_PAD) || \ (CONFIG_KEYPAD == IHIFI_PAD) #define DEBUG_CANCEL BUTTON_LEFT diff --git a/firmware/target/arm/rk27xx/flash-rk27xx.c b/firmware/target/arm/rk27xx/flash-rk27xx.c new file mode 100644 index 0000000000..b5debb0935 --- /dev/null +++ b/firmware/target/arm/rk27xx/flash-rk27xx.c @@ -0,0 +1,836 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2026 by Marcin Bukat + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +/* NAND access for the rk27xx flash translation layer - see flash-rk27xx.h. + * + * THE CONTROLLER + * + * Commands and addresses are written to the chip through FLASH_CMD/ADDR, one + * cycle per write. Data moves a sector at a time between the chip and one of + * four controller slots - 512 bytes of PAGE_BUF and 16 of SPARE_BUF each - + * with the BCH engine in the path: on a read it corrects the sector and + * reports the result in BCHST, on a program it computes the code into the + * last 13 spare bytes. A transfer is started with FLCTL; the slot is chosen + * by bits 3-4. + * + * A read latches a whole page in the chip, then streams its sectors out in + * order from the first: reaching sector k of a page means transferring the + * k before it too. + * + * A program sends the column, the sectors, then the confirm command, and + * reads the chip's status: one program operation per page between erases, + * as this MLC part allows no more (NOP = 1). + * + * The chip's write-protect line is lifted only for the duration of each + * program or erase. */ + +#include "config.h" +#include "system.h" +#include "string.h" +#include "rk27xx.h" +#include "nand-target.h" +#include "flash-rk27xx.h" + +#define CMD_READ_1ST 0x00 +#define CMD_READ_2ND 0x30 +#define CMD_PROG_1ST 0x80 +#define CMD_PROG_2ND 0x10 +#define CMD_PROG_PLANE 0x11 /* end of a two-plane program's first + * page */ +#define CMD_PROG_1ST_P1 0x81 /* start of its second page */ +#define CMD_ERASE_1ST 0x60 +#define CMD_ERASE_2ND 0xD0 +#define CMD_STATUS 0x70 +#define NAND_STATUS_FAIL 0x01 + +#define SPARE_SIZE 16 /* spare bytes per sector on the chip */ +#define SECTOR_STRIDE (FLASH_SECTOR_SIZE + SPARE_SIZE) + +/* Metadata byte 1 of every programmed sector: "programmed" */ +#define META_PROGRAMMED 1 + +/* Transfer one sector between the chip and a controller slot: ECC on, + * transfer on, bit 5 (region select), start. A write adds FL_WR, and the + * BCH engine then encodes (BCH_WR) rather than decodes. */ +#define FL_KICK_READ (FL_COR_EN | FL_XFER_EN | (1<<5) | FL_START) +#define FL_KICK_WRITE (FL_KICK_READ | FL_WR) + +/* Busy limits: the datasheet maxima (tPROG 2.2 ms, tBERS 10 ms) with margin */ +#define PROG_TIMEOUT_US 5000 +#define ERASE_TIMEOUT_US 20000 +/* ...and tDBSY, the busy time after CMD_PROG_PLANE: 1 us at most */ +#define PLANE_TIMEOUT_US 10 + +static struct flash_geometry geo; +static bool ready; +static bool writable; +static bool meta_passthrough; +static bool two_plane_prog; +static uint32_t ecc_mode; /* BCHCTL mode bits: 0 or BCH_T14 */ +static uint32_t boot_area = UINT32_MAX; +static struct flash_stats stats; + +static int prog_finish(void); + +int flash_layer_init(void) +{ + const struct flashspec_t *f = &flash_spec[0]; + int ret = 0; + + ready = false; + + if (f->total_phy_sec == 0 || f->sec_per_page_raw == 0 || + f->sec_per_block_raw == 0) + { + ret = 1; + } + /* the plane interleave below knows one plane or two */ + else if (f->mul_plane != 1 && f->mul_plane != 2) + { + ret = 2; + } + else if (f->sec_per_page_raw * f->mul_plane > FLASH_MAX_SEC_PER_PAGE) + { + ret = 3; + } + else + { + geo.planes = f->mul_plane; + geo.sec_per_page_raw = f->sec_per_page_raw; + geo.sec_per_page = f->sec_per_page; + geo.sec_per_block_raw = f->sec_per_block_raw; + geo.sec_per_block = f->sec_per_block; + geo.total_blocks = f->total_bloks; + geo.total_sectors = f->total_phy_sec; + /* The OF programs both planes' pages at once on every two-plane + * part, its second page with 0x81 - or 0x80 on Toshiba and Micron + * parts, whose plane addressing differs too. Only the 0x81 parts + * (a Samsung one tested) are done here; the others program a page + * at a time. */ + two_plane_prog = f->mul_plane == 2 && + f->vendor != TOSHIBA && f->vendor != MICRON; + ready = true; + } + return ret; +} + +const struct flash_geometry *flash_get_geometry(void) +{ + return &geo; +} + +void flash_set_writable(bool on) +{ + prog_finish(); + writable = on; +} + +void flash_set_boot_area(uint32_t sectors) +{ + boot_area = sectors; +} + +void flash_set_meta_passthrough(bool on) +{ + meta_passthrough = on; +} + +int flash_set_ecc(unsigned t) +{ + int ret = 0; + + if (t == 8) + { + ecc_mode = 0; + } + else if (t == 14) + { + ecc_mode = BCH_T14; + } + else + { + ret = 1; + } + return ret; +} + +void flash_get_stats(struct flash_stats *out) +{ + *out = stats; +} + +/* Map an FTL sector to a raw sector on the chip. + * + * On a two-plane part a super-block is a pair of physical blocks, and + * consecutive pages alternate between them: + * + * super-block sec / (spb_raw * 2) + * within it page = L / (spp_raw * 2) + * plane = (L % (spp_raw * 2)) / spp_raw + * slot = L % spp_raw + * raw = (super_block * 2 + plane) * spb_raw + page * spp_raw + slot + * + * On a single-plane part it is the identity. */ +static uint32_t sec_to_raw(uint32_t sec) +{ + uint32_t raw = sec; + + if (geo.planes == 2) + { + uint32_t spb = (uint32_t)geo.sec_per_block_raw * 2; + uint32_t sb = sec / spb; + uint32_t l = sec % spb; + uint32_t page = l / ((uint32_t)geo.sec_per_page_raw * 2); + uint32_t plane = (l % ((uint32_t)geo.sec_per_page_raw * 2)) + / geo.sec_per_page_raw; + uint32_t slot = l % geo.sec_per_page_raw; + + raw = (sb * 2 + plane) * geo.sec_per_block_raw + + page * geo.sec_per_page_raw + slot; + } + return raw; +} + +static void wait_flash_ready(void) +{ + while (!(FMCTL & FM_RDY)) + { + } +} + +/* R/B# drops within tWB (100 ns) of a confirm command, so wait that long + * before polling, or a still-high line reads as done. */ +static bool wait_ready_us(uint32_t us) +{ + bool timeout = false; + + udelay(1); + while (!(FMCTL & FM_RDY) && !timeout) + { + if (us-- == 0) + { + stats.timeouts++; + timeout = true; + } + else + { + udelay(1); + } + } + return timeout; +} + +static void send_row(uint32_t row) +{ + FLASH_ADDR(0) = row & 0xff; + FLASH_ADDR(0) = (row >> 8) & 0xff; + FLASH_ADDR(0) = (row >> 16) & 0xff; +} + +/* Latch raw page `row` in the chip and prepare the ECC engine to decode in + * `mode` (BCHCTL mode bits). */ +static void latch_page(uint32_t row, uint32_t mode) +{ + prog_finish(); + flash_chip_select(0); + wait_flash_ready(); + + FLASH_CMD(0) = CMD_READ_1ST; + FLASH_ADDR(0) = 0x00; + FLASH_ADDR(0) = 0x00; + send_row(row); + FLASH_CMD(0) = CMD_READ_2ND; + + wait_flash_ready(); + + BCHCTL = BCH_RST | mode; +} + +/* Start the transfer of the next sector of the latched page into slot + * `slot` & 3. */ +static void kick_read(uint32_t slot) +{ + FLCTL = FL_KICK_READ | ((slot & 3) << 3); +} + +/* Wait for the last kicked transfer. Returns its BCH status. */ +static uint32_t wait_read(void) +{ + while (!(FLCTL & FL_RDY)) + { + } + return BCHST; +} + +/* Copy a transferred sector out of slot `slot` & 3. */ +static void copy_slot(uint32_t slot, uint8_t *data, uint8_t *meta) +{ + uint32_t buf = slot & 3; + + if (data) + { + memcpy(data, (const void *)((uintptr_t)&PAGE_BUF + (buf << 9)), + FLASH_SECTOR_SIZE); + } + if (meta) + { + memcpy(meta, (const void *)((uintptr_t)&SPARE_BUF + (buf << 4)), + FLASH_META_SIZE); + } +} + +/* Read sectors [first, first + n) of raw page `row`. Returns 1 if any was + * uncorrectable. */ +static int read_raw_run(uint32_t row, uint32_t first, uint32_t n, + uint8_t *data, uint8_t *meta, uint32_t mode) +{ + uint32_t j; + int uncorrectable = 0; + + latch_page(row, mode); + kick_read(0); + + for (j = 0; j < first + n; j++) + { + bool wanted = j >= first; + uint32_t k = j - first; + uint8_t *d = (wanted && data) ? data + (size_t)k * FLASH_SECTOR_SIZE + : NULL; + uint8_t *m = (wanted && meta) ? meta + (size_t)k * FLASH_META_SIZE + : NULL; + uint32_t st = wait_read(); + + /* the next sector transfers, into the next slot, while this one is + * copied out */ + if (j + 1 < first + n) + { + kick_read(j + 1); + } + copy_slot(j, d, m); + + if (wanted) + { + if (BCH_UNCORRECTABLE(st)) + { + uncorrectable = 1; + } + else if (BCH_CORRECTED(st) >= BCH_REFRESH_THRESHOLD) + { + stats.refresh_pending++; + } + } + } + + flash_chip_deselect(); + return uncorrectable; +} + +int flash_read_raw(uint32_t raw_sec, void *data, void *meta) +{ + int ret = 1; + + if (ready && raw_sec < geo.total_sectors) + { + /* the boot area is t=8 on every device seen, whatever the FTL + * area uses */ + ret = read_raw_run(raw_sec / geo.sec_per_page_raw, + raw_sec % geo.sec_per_page_raw, 1, data, meta, 0); + } + return ret; +} + +int flash_read(uint32_t sec, void *data, void *meta, unsigned n) +{ + uint8_t *d = data; + uint8_t *m = meta; + unsigned i; + int ret = ready ? 0 : 1; + + unsigned run; + + /* One page latch per run of sectors that lie consecutively in one raw + * page. Latching per sector instead cost an array load per sector and, + * the controller streaming a page from its first sector, a transfer of + * every sector before it: 8 loads and 36 transfers for an 8-sector page + * read sector by sector, against 1 and 8. On a two-plane part a run of + * FTL sectors stays in one page until it moves on to the other plane. */ + for (i = 0; i < n && ready; i += run) + { + uint32_t raw = sec_to_raw(sec + i); + uint32_t slot = raw % geo.sec_per_page_raw; + + if (raw >= geo.total_sectors) + { + ret = 1; + break; + } + + run = 1; + while (i + run < n && slot + run < geo.sec_per_page_raw && + sec_to_raw(sec + i + run) == raw + run) + { + run++; + } + + if (read_raw_run(raw / geo.sec_per_page_raw, slot, run, + d ? d + (size_t)i * FLASH_SECTOR_SIZE : NULL, + m ? m + (size_t)i * FLASH_META_SIZE : NULL, ecc_mode)) + { + ret = 1; + } + } + return ret; +} + +/* ---- writing ---- */ + +/* A write is refused in t=14 mode too: a t=14 sector is a 538-byte record + * on the media - 512 data, 3 metadata, 23 parity - where the program path + * below addresses 528-byte records, and programming in that mode has not + * been tried. */ +static bool write_refused(void) +{ + bool refused = !ready || !writable || ecc_mode != 0; + + if (refused) + { + stats.write_refusals++; + } + return refused; +} + +static uint8_t read_status(void) +{ + FLASH_CMD(0) = CMD_STATUS; + return FLASH_DATA(0); +} + +/* The page and spare buffers are written a word at a time; byte stores are + * not known to work. src may be unaligned, or NULL for erased content. */ +static void put_words(uintptr_t dst, const uint8_t *src, uint32_t len) +{ + volatile uint32_t *d = (volatile uint32_t *)dst; + uint32_t i; + + for (i = 0; i < len / 4; i++) + { + if (src == NULL) + { + d[i] = 0xffffffff; + } + else + { + d[i] = src[4*i] | (src[4*i + 1] << 8) | + (src[4*i + 2] << 16) | ((uint32_t)src[4*i + 3] << 24); + } + } +} + +/* `n` sectors of raw page `row`, from sector `first` in it */ +struct prog_run +{ + uint32_t row; + uint32_t first; + uint32_t n; + const uint8_t *data; + const uint8_t *meta; +}; + +/* Send `cmd` and the address of `r`, and load its sectors into the chip's + * page register. */ +static void load_run(uint8_t cmd, const struct prog_run *r) +{ + uint32_t col = r->first * SECTOR_STRIDE; + uint32_t i; + + FLASH_CMD(0) = cmd; + FLASH_ADDR(0) = col & 0xff; + FLASH_ADDR(0) = (col >> 8) & 0xff; + send_row(r->row); + + for (i = 0; i < r->n; i++) + { + uint32_t buf = i & 3; + uint8_t spare[SPARE_SIZE]; + + memset(spare, 0xff, sizeof(spare)); + if (r->meta) + { + memcpy(spare, r->meta + i * FLASH_META_SIZE, FLASH_META_SIZE); + } + if (!meta_passthrough) + { + spare[META_PROGRAMMED] = 0x00; + } + + /* stage this sector while the previous one, in another slot, + * transfers; the BCH engine restarts only once that is done */ + put_words((uintptr_t)&PAGE_BUF + (buf << 9), + r->data ? r->data + (size_t)i * FLASH_SECTOR_SIZE : NULL, + FLASH_SECTOR_SIZE); + put_words((uintptr_t)&SPARE_BUF + (buf << 4), spare, SPARE_SIZE); + + while (!(FLCTL & FL_RDY)) + { + } + + BCHCTL = BCH_WR | BCH_RST; + FLCTL = FL_KICK_WRITE | (buf << 3); + } + + while (!(FLCTL & FL_RDY)) + { + } +} + +/* A program is left running when prog_raw() returns - WP# lifted - and + * finished by prog_finish() at the next chip access or at flash_sync(): its + * tPROG then overlaps whatever the caller does next, most of all the USB + * transfer that follows the last page of a write. */ +static bool prog_pending; +static bool prog_failed; /* a finished program failed: sticky */ + +/* Wait for a pending program and check its status. Returns 1 if it + * failed. */ +static int prog_finish(void) +{ + int fail = 0; + + if (prog_pending) + { + prog_pending = false; + flash_chip_select(0); + fail = wait_ready_us(PROG_TIMEOUT_US) + || (read_status() & NAND_STATUS_FAIL); + flash_chip_deselect(); + FMCTL &= ~FM_PROTECT; + + if (fail) + { + stats.prog_failures++; + prog_failed = true; + } + } + return fail; +} + +int flash_sync(void) +{ + return prog_finish(); +} + +/* Start programming one raw page, or with `planes` 2 a page in each plane - + * r[0] in the first, r[1] the same page of the second - with one two-plane + * program as the OF's FlashProgEnhanced() does: 80h, the first page, 11h, + * tDBSY, 81h, the second page, 10h. The planes then share one tPROG. + * Returns 1 if the program before failed, or this one could not start. */ +static int prog_raw(const struct prog_run *r, unsigned planes) +{ + int fail = prog_finish(); + + flash_chip_select(0); + FMCTL |= FM_PROTECT; /* lift WP# for this operation */ + wait_flash_ready(); + + load_run(CMD_PROG_1ST, &r[0]); + if (planes == 2) + { + FLASH_CMD(0) = CMD_PROG_PLANE; + if (wait_ready_us(PLANE_TIMEOUT_US)) + { + flash_chip_deselect(); + FMCTL &= ~FM_PROTECT; + stats.prog_failures++; + prog_failed = true; + return 1; + } + load_run(CMD_PROG_1ST_P1, &r[1]); + } + + FLASH_CMD(0) = CMD_PROG_2ND; + prog_pending = true; + flash_chip_deselect(); + return fail; +} + +static int erase_raw_block(uint32_t row) +{ + int fail; + + prog_finish(); + flash_chip_select(0); + FMCTL |= FM_PROTECT; + wait_flash_ready(); + + FLASH_CMD(0) = CMD_ERASE_1ST; + send_row(row); + FLASH_CMD(0) = CMD_ERASE_2ND; + + fail = wait_ready_us(ERASE_TIMEOUT_US) + || (read_status() & NAND_STATUS_FAIL); + + flash_chip_deselect(); + FMCTL &= ~FM_PROTECT; + + if (fail) + { + stats.erase_failures++; + } + return fail ? 1 : 0; +} + +/* Fill `r` with the run of sectors from sec + i, up to sec + n, that lie + * consecutively in one raw page. Returns its length, or 0 past the chip. */ +static uint32_t get_run(uint32_t sec, uint32_t i, uint32_t n, + const uint8_t *data, const uint8_t *meta, + struct prog_run *r) +{ + uint32_t raw = sec_to_raw(sec + i); + uint32_t run = 1; + + if (raw >= geo.total_sectors) + { + return 0; + } + + r->row = raw / geo.sec_per_page_raw; + r->first = raw % geo.sec_per_page_raw; + while (i + run < n && r->first + run < geo.sec_per_page_raw && + sec_to_raw(sec + i + run) == raw + run) + { + run++; + } + r->n = run; + r->data = data ? data + (size_t)i * FLASH_SECTOR_SIZE : NULL; + r->meta = meta ? meta + (size_t)i * FLASH_META_SIZE : NULL; + return run; +} + +/* Sectors of the FTL's view are split into one program per raw page, or + * where they cover the same page of both planes, one per pair of pages. */ +int flash_program(uint32_t sec, const void *data, const void *meta, unsigned n) +{ + uint32_t ppb_raw = geo.sec_per_block_raw / geo.sec_per_page_raw; + uint32_t i = 0; + int ret = 0; + + if (n == 0) + { + ret = 0; + } + else if (write_refused()) + { + ret = 1; + } + else if (sec < boot_area) + { + stats.boot_area_skips++; + } + else + { + while (i < n && ret == 0) + { + struct prog_run r[2]; + uint32_t len = get_run(sec, i, n, data, meta, &r[0]); + unsigned planes = 1; + + if (len == 0) + { + ret = 1; + break; + } + + /* sec_to_raw() puts a page's second plane in the next block */ + if (two_plane_prog && i + len < n && + get_run(sec, i + len, n, data, meta, &r[1]) != 0 && + r[1].row == r[0].row + ppb_raw) + { + len += r[1].n; + planes = 2; + } + + if (prog_raw(r, planes)) + { + ret = 1; + } + i += len; + } + } + return ret; +} + +int flash_program_page(uint32_t sec, const void *data, const void *meta) +{ + return flash_program(sec & ~(uint32_t)(geo.sec_per_page - 1), data, meta, + geo.sec_per_page); +} + +int flash_erase(uint32_t sec) +{ + uint32_t base = sec - sec % geo.sec_per_block; + uint32_t p; + int ret = 0; + + if (write_refused()) + { + ret = 1; + } + else if (sec < boot_area) + { + stats.boot_area_skips++; + } + else if (sec_to_raw(base) >= geo.total_sectors) + { + ret = 1; + } + else + { + for (p = 0; p < geo.planes; p++) + { + uint32_t raw = sec_to_raw(base + p * geo.sec_per_page_raw); + + if (erase_raw_block(raw / geo.sec_per_page_raw)) + { + ret = 1; + } + } + } + return ret; +} + +/* What a copy programs as the destination's metadata */ +enum copy_meta +{ + COPY_META_FRESH, /* as flash_program() with meta NULL */ + COPY_META_KEEP, /* each sector's own */ + COPY_META_PAGE, /* the caller's, by position in the page */ +}; + +/* Copy through the ECC engine, never with the chip's internal data move: on + * this MLC part moved pages accumulate bit errors, and the data area's + * t=8 is already below the chip's 12-bit minimum. A source sector that + * fails ECC is copied as read, and counted. */ +static int copy_sectors(uint32_t src, uint32_t dst, unsigned n, + enum copy_meta how, const uint8_t *page_meta) +{ + static uint8_t buf[FLASH_MAX_SEC_PER_PAGE * FLASH_SECTOR_SIZE] + __attribute__((aligned(4))); + static uint8_t meta[FLASH_MAX_SEC_PER_PAGE * FLASH_META_SIZE]; + uint32_t k = 0; + int ret = 0; + + if (n == 0 || n > geo.sec_per_block) + { + ret = 0; + } + else if (write_refused()) + { + ret = 1; + } + else + { + /* a copy reports its own programs' results, the last one included: + * the FTL moves the data elsewhere when one fails */ + prog_finish(); + prog_failed = false; + + while (k < n && ret == 0) + { + /* one destination page of every plane at a time, for + * flash_program() to program the planes together */ + uint32_t len = geo.sec_per_page - (dst + k) % geo.sec_per_page; + uint32_t i = 0; + + if (len > n - k) + { + len = n - k; + } + + /* the source may straddle raw pages if its offset differs */ + while (i < len && ret == 0) + { + uint32_t raw = sec_to_raw(src + k + i); + uint32_t first = raw % geo.sec_per_page_raw; + uint32_t run = 1; + + if (raw >= geo.total_sectors) + { + ret = 1; + break; + } + while (i + run < len && first + run < geo.sec_per_page_raw && + sec_to_raw(src + k + i + run) == raw + run) + { + run++; + } + + if (read_raw_run(raw / geo.sec_per_page_raw, first, run, + buf + (size_t)i * FLASH_SECTOR_SIZE, + how == COPY_META_KEEP ? + meta + (size_t)i * FLASH_META_SIZE : NULL, + ecc_mode)) + { + stats.copy_uncorrectable++; + } + i += run; + } + + if (how == COPY_META_PAGE) + { + for (i = 0; i < len; i++) + { + uint32_t at = (dst + k + i) % geo.sec_per_page; + + memcpy(meta + i * FLASH_META_SIZE, + page_meta + at * FLASH_META_SIZE, + FLASH_META_SIZE); + } + } + + if (ret == 0 + && flash_program(dst + k, buf, + how == COPY_META_FRESH ? NULL : meta, len)) + { + ret = 1; + } + k += len; + } + + prog_finish(); + if (prog_failed) + { + ret = 1; + } + } + return ret; +} + +int flash_copy(uint32_t src, uint32_t dst, unsigned n) +{ + return copy_sectors(src, dst, n, COPY_META_FRESH, NULL); +} + +int flash_copy_meta(uint32_t src, uint32_t dst, unsigned n, + const void *page_meta) +{ + return copy_sectors(src, dst, n, + page_meta ? COPY_META_PAGE : COPY_META_KEEP, + page_meta); +} diff --git a/firmware/target/arm/rk27xx/flash-rk27xx.h b/firmware/target/arm/rk27xx/flash-rk27xx.h new file mode 100644 index 0000000000..eacfe456d7 --- /dev/null +++ b/firmware/target/arm/rk27xx/flash-rk27xx.h @@ -0,0 +1,143 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2026 by Marcin Bukat + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +/* NAND access for the rk27xx flash translation layer. + * + * This layer drives the NAND controller and its BCH engine and nothing else: + * it knows the chip's geometry and how to read, program, erase and copy + * sectors, but not what the sectors mean. The FTL (ftl-scheme-a.c) is built + * on it. + * + * ADDRESSES are sector numbers (512 bytes) in the FTL's linear view of the + * chip, where a block is a SUPER-BLOCK: on a two-plane part, one physical + * block from each plane, with consecutive pages alternating between the + * planes. This layer maps that view to the chip; the FTL never sees planes. + * + * METADATA: every sector carries 16 spare bytes. The last 13 are the BCH + * code and belong to the hardware; the first three are the FTL's, and are + * what the meta arguments below carry - FLASH_META_SIZE bytes per sector. + * Byte 1 is special: by default this layer writes 0x00 there on every + * program, so that the FTL can tell a programmed page from an erased one + * whatever it wrote. An FTL that keeps its own data in byte 1 turns that off + * with flash_set_meta_passthrough(). + * + * Only the first chip is supported; every device the FTL has been validated + * on has one. */ + +#ifndef __FLASH_RK27XX_H__ +#define __FLASH_RK27XX_H__ + +#include +#include + +#define FLASH_SECTOR_SIZE 512 +#define FLASH_META_SIZE 3 /* FTL metadata bytes per sector */ + +/* The largest page the layer handles: 8 sectors per plane, two planes. */ +#define FLASH_MAX_SEC_PER_PAGE 16 + +struct flash_geometry +{ + uint8_t planes; /* 1 or 2 */ + uint8_t sec_per_page_raw; /* sectors in one physical page */ + uint8_t sec_per_page; /* sectors in one page of every plane */ + uint16_t sec_per_block_raw; /* sectors in one physical block */ + uint16_t sec_per_block; /* sectors in one super-block */ + uint32_t total_blocks; /* super-blocks on the chip */ + uint32_t total_sectors; +}; + +/* Write-path error counters. Most write results do not change what the FTL + * does next - it has no way to recover from most of them - so these are the + * record that something went wrong. */ +struct flash_stats +{ + uint32_t prog_failures; /* programs the chip reported failed */ + uint32_t erase_failures; + uint32_t timeouts; /* the chip never became ready */ + uint32_t boot_area_skips; /* writes into the boot area, dropped */ + uint32_t write_refusals; /* writes attempted while not writable */ + uint32_t copy_uncorrectable;/* copy sources failing ECC, copied anyway */ + uint32_t refresh_pending; /* reads near the ECC limit: blocks decaying */ +}; + +/* Take the geometry flash_init() detected. Returns 0 if the chip is usable. */ +int flash_layer_init(void); +const struct flash_geometry *flash_get_geometry(void); + +/* Read n sectors from sec. data (n * 512 bytes) and meta (n * 3 bytes) may + * each be NULL. Returns 0, or 1 if any sector was uncorrectable - its data + * is then delivered as read. */ +int flash_read(uint32_t sec, void *data, void *meta, unsigned n); + +/* Program n sectors from sec, which must be erased. data NULL programs + * 0xff; meta NULL programs {0xff, 0x00, 0xff} - {0xff, 0xff, 0xff} with + * passthrough on. The last program is left running: its result arrives + * with the next flash call - a program returns 1 if the one before failed - + * or with flash_sync(). Returns 0 or 1 on failure. */ +int flash_program(uint32_t sec, const void *data, const void *meta, unsigned n); + +/* Wait for the program left running, if any. Returns 1 if it failed. */ +int flash_sync(void); + +/* Program the whole page (every plane) that contains sec. */ +int flash_program_page(uint32_t sec, const void *data, const void *meta); + +/* Erase the super-block containing sec. */ +int flash_erase(uint32_t sec); + +/* Copy n sectors (at most one super-block) from src to dst through the ECC + * engine, so that correctable errors are not propagated. The destination + * gets fresh metadata {0xff, 0x00, 0xff}. */ +int flash_copy(uint32_t src, uint32_t dst, unsigned n); + +/* As flash_copy(), but with chosen metadata: page_meta NULL keeps each + * sector's own; otherwise sector k of every destination page is programmed + * with page_meta[k] (sec_per_page * FLASH_META_SIZE bytes). */ +int flash_copy_meta(uint32_t src, uint32_t dst, unsigned n, + const void *page_meta); + +/* Program metadata byte 1 as given rather than 0x00. */ +void flash_set_meta_passthrough(bool on); + +/* The BCH strength of the FTL's area: t = 8 (the default) or 14, the + * controller's BCH_T14 mode. flash_read_raw() always reads t=8, as the + * boot area is on every device seen. Writing is refused in t=14 mode: its + * sectors are 538-byte records on the media (3 + 23 spare bytes), and + * programming them has not been tried. + * Returns 0, or 1 for an unsupported t. */ +int flash_set_ecc(unsigned t); + +/* Read one PHYSICAL sector, bypassing the super-block view: for the boot + * area, which the boot ROM addresses physically. */ +int flash_read_raw(uint32_t raw_sec, void *data, void *meta); + +/* Writes - program, erase and copy - are refused (return 1, counted) until + * the FTL has confirmed it understands the media and enables them. */ +void flash_set_writable(bool writable); + +/* The first `sectors` sectors hold the boot area. A write there is dropped + * and reported successful, as the original firmware does: no FTL error may + * reach the bootloader. UINT32_MAX protects the whole chip. */ +void flash_set_boot_area(uint32_t sectors); + +void flash_get_stats(struct flash_stats *stats); + +#endif /* __FLASH_RK27XX_H__ */ diff --git a/firmware/target/arm/rk27xx/ftl-probe-rk27xx.c b/firmware/target/arm/rk27xx/ftl-probe-rk27xx.c new file mode 100644 index 0000000000..3b04d08f65 --- /dev/null +++ b/firmware/target/arm/rk27xx/ftl-probe-rk27xx.c @@ -0,0 +1,219 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2026 by Marcin Bukat + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +/* The FTL scheme finder - see ftl-probe-rk27xx.h. + * + * The two formats tell themselves apart in the metadata of a block's first + * sector (flash-rk27xx.h): Scheme A marks its remap-log blocks with type + * 0x52 in byte 2 (ftl-scheme-a.c), Scheme B keeps a 0xfxxx tag in bytes 0-1 + * - 0xf000 for its bad-block table near the start of the chip, 0xf100 and + * 0xf200 for its cache and data blocks (ftl-scheme-b.c). Every zone of + * Scheme A has two log blocks, the first zone within its first 256 blocks; + * Scheme B's table lies within 50 blocks of the boot area. So the first + * SCAN_BLOCKS blocks decide. + * + * They are read in the BCH mode the FTL's area uses, which need not be the + * boot area's t=8: the Archos Vision writes its FTL area with t=14. ID + * block 1 of the later generation - 'RK27' at 0x0a - records it at 0x1ed + * (8 on the HM-601, 14 on the Archos); the earlier generation's ID block + * has no such field, and its devices are t=8 throughout. */ + +#include +#include + +#include "config.h" +#include "nand-target.h" +#include "flash-rk27xx.h" +#include "ftl-probe-rk27xx.h" + +#define SCAN_BLOCKS 512 + +/* ID blocks: every IDB_STRIDE-th raw sector of the boot area, metadata type + * IDB_TYPE; ID block 1 is the sector after (ftl-rk27xx.c) */ +#define IDB_STRIDE 512 +#define IDB_POSITIONS 50 +#define IDB_TYPE 0x69 +#define IDB1_MAGIC 0x0a /* 'RK27' in the later generation's ... */ +#define IDB1_ECC_T 0x1ed /* ... which records the BCH t here */ +#define DEFAULT_ECC_T 8 + +#define A_LOG_TYPE 0x52 +#define B_TAG_TABLE 0xf000 +#define B_TAG_CACHE 0xf100 +#define B_TAG_DATA 0xf200 + +static void probe_idb(struct ftl_probe *p) +{ + uint8_t data[FLASH_SECTOR_SIZE], meta[FLASH_META_SIZE]; + uint32_t pos; + bool found = false; + + for (pos = 0; pos < IDB_POSITIONS && !found; pos++) + { + uint32_t raw = pos * IDB_STRIDE; + + if (flash_read_raw(raw, data, meta) == 0 && meta[2] == IDB_TYPE && + flash_read_raw(raw + 1, data, meta) == 0) + { + p->idb_boot_blocks = (uint16_t)(data[0] | data[1] << 8); + p->idb_sys_mb = (uint16_t)(data[2] | data[3] << 8); + p->idb_data_mb = (uint16_t)(data[4] | data[5] << 8); + p->idb_rk27 = memcmp(data + IDB1_MAGIC, "RK27", 4) == 0; + if (p->idb_rk27) + { + p->idb_ecc_t = data[IDB1_ECC_T]; + } + found = true; + } + } +} + +static void probe_block(struct ftl_probe *p, uint32_t blk) +{ + uint8_t meta[FLASH_META_SIZE]; + uint16_t tag; + + if (flash_read(blk * p->sec_per_block, NULL, meta, 1) != 0) + { + p->unreadable++; + } + else + { + tag = (uint16_t)(meta[0] | meta[1] << 8); + + /* tag 0xffff: erased, or a block only its byte 2 marks - an ID + * block, type 0x69 */ + if (tag == 0xffff) + { + p->erased++; + } + else if (meta[0] == 0xff && meta[1] == 0x00 && meta[2] == A_LOG_TYPE) + { + p->a_logs++; + } + else if (meta[1] == 0x00) + { + /* Scheme A's flash layer zeroes byte 1 of every page */ + p->a_blocks++; + } + else if (tag == B_TAG_TABLE) + { + if (p->b_tables++ == 0) + { + p->first_b_table = (uint16_t)blk; + } + } + else if ((tag & 0xff00) == B_TAG_CACHE) + { + p->b_cache++; + } + else if ((tag & 0xff00) == B_TAG_DATA) + { + p->b_data++; + } + else if ((tag & 0xf000) == 0xf000) + { + if (p->b_other++ == 0) + { + p->first_b_other = tag; + } + } + else if (p->other++ == 0) + { + p->first_other = (uint16_t)blk; + memcpy(p->first_other_meta, meta, sizeof(p->first_other_meta)); + } + } +} + +void ftl_probe(struct ftl_probe *p) +{ + const struct flash_geometry *geo; + uint32_t blk; + + memset(p, 0, sizeof(*p)); + p->first_b_table = 0xffff; + + flash_init(); + p->flash_error = flash_layer_init(); + + if (p->flash_error == 0) + { + geo = flash_get_geometry(); + p->planes = geo->planes; + p->sec_per_page = geo->sec_per_page; + p->sec_per_block = geo->sec_per_block; + p->blocks = geo->total_blocks; + + probe_idb(p); + + /* a value the controller has no mode for is reported, and t=8 used */ + p->scan_ecc_t = DEFAULT_ECC_T; + if (p->idb_rk27 && flash_set_ecc(p->idb_ecc_t) == 0) + { + p->scan_ecc_t = p->idb_ecc_t; + } + + for (blk = 0; blk < SCAN_BLOCKS && blk < p->blocks; blk++) + { + probe_block(p, blk); + } + p->scanned = (uint16_t)blk; + flash_set_ecc(DEFAULT_ECC_T); + + if (p->b_tables > 0 && p->b_data > 0) + { + p->scheme = p->b_other > 0 ? FTL_PROBE_SCHEME_B_OTHER + : FTL_PROBE_SCHEME_B; + } + else if (p->a_logs >= 2 && p->b_tables == 0) + { + p->scheme = FTL_PROBE_SCHEME_A; + } + else if (p->unreadable > p->scanned / 2) + { + p->scheme = FTL_PROBE_UNREADABLE; + } + } +} + +const char *ftl_probe_scheme_name(enum ftl_probe_scheme scheme) +{ + const char *name = "unknown"; + + switch (scheme) + { + case FTL_PROBE_SCHEME_A: + name = "A"; + break; + case FTL_PROBE_SCHEME_B: + name = "B"; + break; + case FTL_PROBE_UNREADABLE: + name = "unknown, ECC fails"; + break; + case FTL_PROBE_SCHEME_B_OTHER: + name = "B, other generation"; + break; + default: + break; + } + return name; +} diff --git a/firmware/target/arm/rk27xx/ftl-probe-rk27xx.h b/firmware/target/arm/rk27xx/ftl-probe-rk27xx.h new file mode 100644 index 0000000000..cabd75a310 --- /dev/null +++ b/firmware/target/arm/rk27xx/ftl-probe-rk27xx.h @@ -0,0 +1,83 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2026 by Marcin Bukat + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +/* Which flash translation layer formatted an rk27xx target's NAND - for + * targets whose config does not define CONFIG_RK27XX_FTL yet. Read-only: + * it looks at the first page of the first blocks and nothing else. */ + +#ifndef __FTL_PROBE_RK27XX_H__ +#define __FTL_PROBE_RK27XX_H__ + +#include +#include + +enum ftl_probe_scheme +{ + FTL_PROBE_UNKNOWN = 0, + FTL_PROBE_SCHEME_A, /* remap-log blocks: ftl-scheme-a.c */ + FTL_PROBE_SCHEME_B, /* bad-block table and data headers: + * ftl-scheme-b.c */ + FTL_PROBE_SCHEME_B_OTHER, /* Scheme B tags of another generation */ + FTL_PROBE_UNREADABLE, /* most blocks fail ECC in the mode used */ +}; + +struct ftl_probe +{ + enum ftl_probe_scheme scheme; + int flash_error; /* flash_layer_init() result; 0 = chip found */ + + /* the chip */ + uint8_t planes; + uint8_t sec_per_page; /* both planes */ + uint16_t sec_per_block; + uint32_t blocks; + + /* ID block 1, 0 if none found */ + uint16_t idb_boot_blocks; /* raw blocks */ + uint16_t idb_sys_mb; + uint16_t idb_data_mb; + bool idb_rk27; /* the later layout, with 'RK27' at 0x0a ... */ + uint8_t idb_ecc_t; /* ... and the FTL area's BCH t at 0x1ed */ + + uint8_t scan_ecc_t; /* the BCH t the blocks were read with */ + + /* what page 0 of the blocks scanned holds */ + uint16_t scanned; + uint16_t unreadable; + uint16_t erased; + uint16_t a_logs; /* Scheme A remap-log blocks */ + uint16_t a_blocks; /* other blocks Scheme A has programmed */ + uint16_t b_tables; /* Scheme B: 0xf000 bad-block table */ + uint16_t b_cache; /* 0xf1xx */ + uint16_t b_data; /* 0xf2xx */ + uint16_t b_other; /* other 0xfxxx tags */ + uint16_t other; /* none of these */ + uint16_t first_b_table; /* block of the first 0xf000; 0xffff = none */ + uint16_t first_b_other; /* the first other 0xfxxx tag; 0 = none */ + uint16_t first_other; /* first block of none of these ... */ + uint8_t first_other_meta[3]; /* ... and its metadata */ +}; + +/* Run the probe; takes a fraction of a second */ +void ftl_probe(struct ftl_probe *p); + +const char *ftl_probe_scheme_name(enum ftl_probe_scheme scheme); + +#endif /* __FTL_PROBE_RK27XX_H__ */ diff --git a/firmware/target/arm/rk27xx/ftl-rk27xx.c b/firmware/target/arm/rk27xx/ftl-rk27xx.c index 82a26b7865..fcf1ffea89 100644 --- a/firmware/target/arm/rk27xx/ftl-rk27xx.c +++ b/firmware/target/arm/rk27xx/ftl-rk27xx.c @@ -7,6 +7,7 @@ * \/ \/ \/ \/ \/ * * Copyright (C) 2010 by Bertrik Sikken + * Copyright (C) 2026 by Marcin Bukat * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -17,39 +18,383 @@ * KIND, either express or implied. * ****************************************************************************/ - -#include "config.h" -#include "ftl-target.h" -/* this file provides empty STUBS for now */ +/* Rockbox storage on the rk27xx NAND, through the FTL scheme the target's + * config names in CONFIG_RK27XX_FTL: ftl-scheme-a.c or ftl-scheme-b.c. + * + * Writing is opt-in: a build without FTL_ALLOW_WRITE mounts read-only and + * never writes the flash, not even the repairs a mount can make. */ + +#include "config.h" +#include "system.h" +#include "ftl-target.h" +#include "nand-target.h" +#include "flash-rk27xx.h" +#ifdef FTL_SYS_ON_USB +#include "usb.h" +#endif + +#if CONFIG_RK27XX_FTL == RK27XX_FTL_SCHEME_A +#include "ftl-scheme-a.h" +#elif CONFIG_RK27XX_FTL == RK27XX_FTL_SCHEME_B +#include "ftl-scheme-b.h" +#else +#error "NAND storage needs CONFIG_RK27XX_FTL in the target config" +#endif + +/* The boot ROM looks for ID blocks at every 512th raw sector of the boot + * area, up to 50 positions, by metadata type 0x69. */ +#define IDB_STRIDE 512 +#define IDB_POSITIONS 50 +#define IDB_META_TYPE 2 +#define IDB_TYPE 0x69 +#define IDB_MAX_BOOT_BLOCKS 64 + +#define SECTORS_PER_MB 2048 + +#if CONFIG_RK27XX_FTL == RK27XX_FTL_SCHEME_B +/* Scheme B's original firmware keeps its open exchange blocks to 8, and its + * mount recovers no more (ftl-scheme-b.h) */ +#define SCHEME_B_EXCH_BLOCKS 8 +#endif + +/* What ID block 1 records */ +struct idb_info +{ + uint32_t boot_blocks; /* raw blocks of the boot area */ + uint32_t sys_sectors; /* the SYS volume (code disk) */ + uint32_t data_sectors; /* Scheme B: the system data area after it */ +}; + +static bool ftl_mounted = false; + +/* The volumes behind the drives. Both are known to the FTL even when SYS + * is not a drive. */ +#if CONFIG_RK27XX_FTL == RK27XX_FTL_SCHEME_A +#define VOL_SYS FTL_A_VOL_SYS +#define VOL_USER FTL_A_VOL_USER +#else +#define VOL_SYS 0 +#define VOL_USER 1 +#define NUM_VOLS 2 + +/* Scheme B's logical space holds the volumes back to back: SYS, the system + * data area, then USER to the end */ +static uint32_t vol_base[NUM_VOLS]; +static uint32_t vol_size[NUM_VOLS]; +#endif + +#ifdef FTL_SYS_ON_USB +/* Asked for in the debug menu: show SYS through the USER drive while a USB + * host has the storage. Decided once per connection, so asking during one + * waits for the next, and used up when that connection ends. */ +static bool sys_requested = false; +static enum { USB_VIEW_NONE, USB_VIEW_USER, USB_VIEW_SYS } usb_view; + +static bool sys_on_usb(void) +{ + if (!usb_exclusive_storage()) + { + if (usb_view == USB_VIEW_SYS) + { + sys_requested = false; + } + usb_view = USB_VIEW_NONE; + } + else if (usb_view == USB_VIEW_NONE) + { + usb_view = sys_requested ? USB_VIEW_SYS : USB_VIEW_USER; + } + return usb_view == USB_VIEW_SYS; +} + +void ftl_set_sys_on_usb(bool on) +{ + sys_requested = on; +} + +bool ftl_get_sys_on_usb(void) +{ + return sys_requested; +} +#endif + +static int drive_volume(int drive) +{ +#ifdef HAVE_RK27XX_NAND_SYS + return (drive == FTL_DRIVE_SYS) ? VOL_SYS : VOL_USER; +#else + (void)drive; +#ifdef FTL_SYS_ON_USB + if (sys_on_usb()) + { + return VOL_SYS; + } +#endif + return VOL_USER; +#endif +} + +/* ID block 1, the sector after ID block 0: + * + * +0 uint16_t LE raw blocks of the boot area + * +2 uint16_t LE SYS volume size, MB + * +4 uint16_t LE system data area size, MB (Scheme B) + * + * ID block 0 is scrambled; ID block 1 is plain. A sector marked 0x69 whose + * ID block 1 gives a sane block count and a SYS volume smaller than the + * chip is taken; descrambling ID block 0 to check its signature would buy + * little over that. Returns false if none is found. */ +static bool idb_read(struct idb_info *idb) +{ + const struct flash_geometry *geo = flash_get_geometry(); + uint8_t data[FLASH_SECTOR_SIZE], meta[FLASH_META_SIZE]; + bool found = false; + uint32_t pos; + + for (pos = 0; pos < IDB_POSITIONS && !found; pos++) + { + uint32_t raw = pos * IDB_STRIDE; + uint32_t blocks, mb; + + if (raw + 1 >= geo->total_sectors) + { + break; + } + + if (flash_read_raw(raw, data, meta) != 0 + || meta[IDB_META_TYPE] != IDB_TYPE) + { + continue; + } + if (flash_read_raw(raw + 1, data, meta) != 0) + { + continue; + } + + blocks = data[0] | (data[1] << 8); + mb = data[2] | (data[3] << 8); + + if (blocks > 0 && blocks <= IDB_MAX_BOOT_BLOCKS && mb > 0 && + mb * SECTORS_PER_MB < geo->total_sectors) + { + idb->boot_blocks = blocks; + idb->sys_sectors = mb * SECTORS_PER_MB; + idb->data_sectors = (data[4] | (data[5] << 8)) * SECTORS_PER_MB; + found = true; + } + } + return found; +} + +#if CONFIG_RK27XX_FTL == RK27XX_FTL_SCHEME_A +static uint32_t mount_scheme(const struct idb_info *idb) +{ + struct ftl_a_config config; + uint32_t ret = 0; + + config.sys_sectors = idb->sys_sectors; +#ifdef FTL_ALLOW_WRITE + config.read_only = false; + /* the rk2705 NAND bootloader's generation formats with flag 1; its + * write logic is the same as the standard one's */ + config.alt_format_flag = 1; + config.alt_format_writable = true; +#else + config.read_only = true; + config.alt_format_flag = 1; + config.alt_format_writable = false; +#endif + + if (ftl_a_mount(&config) != FTL_A_OK) + { + ret = 3; + } + else if (ftl_a_capacity(FTL_A_VOL_USER) == 0) + { + ret = 4; + } + return ret; +} +#else +static uint32_t mount_scheme(const struct idb_info *idb) +{ + const struct flash_geometry *geo = flash_get_geometry(); + struct ftl_b_config config; + uint32_t user_base = idb->sys_sectors + idb->data_sectors; + uint32_t ret = 0; + + config.first_block = (uint16_t)(idb->boot_blocks / geo->planes); + config.exch_blocks = SCHEME_B_EXCH_BLOCKS; +#ifdef FTL_ALLOW_WRITE + config.read_only = false; +#else + config.read_only = true; +#endif + + if (ftl_b_mount(&config) != FTL_B_OK) + { + ret = 3; + } + else if (ftl_b_capacity() <= user_base) + { + ret = 4; + } + else + { + vol_base[VOL_SYS] = 0; + vol_size[VOL_SYS] = idb->sys_sectors; + vol_base[VOL_USER] = user_base; + vol_size[VOL_USER] = ftl_b_capacity() - user_base; + } + return ret; +} +#endif uint32_t ftl_init(void) { - /* TODO implement */ - return 0; + struct idb_info idb; + uint32_t ret = 0; + + flash_init(); + + if (flash_layer_init() != 0) + { + ret = 1; + } + else if (!idb_read(&idb)) + { + ret = 2; /* without it USER cannot be told from SYS */ + } + else + { + ret = mount_scheme(&idb); + ftl_mounted = ret == 0; + } + return ret; } -uint32_t ftl_read(uint32_t sector, uint32_t count, void* buffer) +static bool drive_valid(int drive) { - /* TODO implement */ - (void)sector; - (void)count; - (void)buffer; - return 0; + return ftl_mounted && drive >= 0 && drive < FTL_NUM_DRIVES; } -uint32_t ftl_write(uint32_t sector, uint32_t count, const void* buffer) +#if CONFIG_RK27XX_FTL == RK27XX_FTL_SCHEME_A +static uint32_t vol_sectors(int vol) { - /* TODO implement */ - (void)sector; - (void)count; - (void)buffer; - return 0; + return ftl_a_capacity(vol); +} + +static int vol_read(int vol, uint32_t sector, uint32_t count, void *buffer) +{ + return ftl_a_read(vol, sector, buffer, count); +} + +#ifdef FTL_ALLOW_WRITE +static int vol_write(int vol, uint32_t sector, uint32_t count, + const void *buffer) +{ + return ftl_a_write(vol, sector, buffer, count); +} +#endif + +static void vol_sync(void) +{ + ftl_a_sync(); +} +#else +static uint32_t vol_sectors(int vol) +{ + return vol_size[vol]; +} + +static bool in_volume(int vol, uint32_t sector, uint32_t count) +{ + return sector < vol_size[vol] && count <= vol_size[vol] - sector; +} + +static int vol_read(int vol, uint32_t sector, uint32_t count, void *buffer) +{ + int ret = 1; + + if (in_volume(vol, sector, count)) + { + ret = ftl_b_read(vol_base[vol] + sector, buffer, count); + } + return ret; +} + +#ifdef FTL_ALLOW_WRITE +static int vol_write(int vol, uint32_t sector, uint32_t count, + const void *buffer) +{ + int ret = 1; + + if (in_volume(vol, sector, count)) + { + ret = ftl_b_write(vol_base[vol] + sector, buffer, count); + } + return ret; +} +#endif + +static void vol_sync(void) +{ + ftl_b_sync(); +} +#endif + +uint32_t ftl_get_sectors(int drive) +{ + uint32_t sectors = 0; + + if (drive_valid(drive)) + { + sectors = vol_sectors(drive_volume(drive)); + } + return sectors; +} + +bool ftl_drive_is_sys(int drive) +{ + return drive_valid(drive) && drive_volume(drive) == VOL_SYS; +} + +uint32_t ftl_read(int drive, uint32_t sector, uint32_t count, void *buffer) +{ + uint32_t ret = 1; + + if (drive_valid(drive)) + { + ret = vol_read(drive_volume(drive), sector, count, buffer) ? 2 : 0; + } + return ret; +} + +uint32_t ftl_write(int drive, uint32_t sector, uint32_t count, + const void *buffer) +{ + uint32_t ret = 1; + +#ifdef FTL_ALLOW_WRITE + if (drive_valid(drive)) + { + ret = vol_write(drive_volume(drive), sector, count, buffer) ? 2 : 0; + } +#else + /* refuse rather than pretend: a silent success would let the filesystem + * believe data was committed */ + (void)drive; (void)sector; (void)count; (void)buffer; +#endif + return ret; } uint32_t ftl_sync(void) { - /* TODO implement */ + if (ftl_mounted) + { + vol_sync(); + } + flash_sync(); return 0; } - diff --git a/firmware/target/arm/rk27xx/ftl-scheme-a.c b/firmware/target/arm/rk27xx/ftl-scheme-a.c new file mode 100644 index 0000000000..5d71866aff --- /dev/null +++ b/firmware/target/arm/rk27xx/ftl-scheme-a.c @@ -0,0 +1,2136 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2026 by Marcin Bukat + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +/* THE SCHEME A FLASH TRANSLATION LAYER + * + * Many rk2705/rk2706 players store everything - their firmware and the + * user's music - on NAND flash managed by a block-mapping FTL, called Scheme + * A here to tell it from the log-structured layout later firmware uses. This + * file reads and writes that format exactly as the original firmware does, + * so a device keeps working with its original firmware after Rockbox has + * written to it. Where the original firmware's behaviour is odd, it is + * reproduced on purpose and marked as such. + * + * + * VOLUMES + * + * The FTL presents one linear space of 512-byte logical sectors, split into + * two volumes: + * + * SYS the first sectors. The original firmware keeps itself here + * (BASE.RKW and its resources) as a FAT volume of its own. + * USER everything after SYS: the music storage, the drive the original + * firmware exports over USB. + * + * Where SYS ends is not in the FTL's own tables: the boot area's ID block + * records it (see ftl-rk27xx.c), and the caller passes it in. + * + * + * BLOCKS AND ZONES + * + * The unit of mapping is the SUPER-BLOCK - on a two-plane chip, a physical + * block from each plane, used as one (flash-rk27xx.h). Blocks are grouped in + * ZONES of 256; a 4096-block chip has 16. Mapping never crosses a zone: a + * logical block always lives somewhere in its own zone, so a zone can be + * described by one byte per block. + * + * The first blocks of the chip are the BOOT AREA: the bootloader and the ID + * blocks the boot ROM looks for. The FTL does not use them. The zone they are + * in is the SYSTEM ZONE (zone 0 on every device seen), and the number of boot + * blocks in it is the SYSTEM OFFSET. + * + * + * THE ZONE TABLE + * + * Each zone is described by a 512-byte table. Its first 256 bytes are block + * positions within the zone: + * + * [0 .. n-1] logical block i is stored at position table[i]; + * n is the zone's block count (see below) + * [base - 1] system zone only: the exchange-record block + * [base .. base+5] the exchange pool: six erased blocks, one per + * exchange slot, that writes go to + * [base + 6] the spare block power-loss recovery merges into + * [0xfe], [0xff] the zone's two remap-log blocks: mirror, primary + * + * Positions not listed are bad or unused. The table is a permutation - no + * block appears twice - and writing only ever swaps entries. + * + * In the system zone every index above except the log entries' and the + * format flag's counts DOWN from the top shifted by the system offset: the + * top entries are taken by the boot blocks. So the primary log block of the + * system zone is at table[0xff - offset], its pool at table[base - offset]. + * + * The BASE, the first pool entry, depends on the firmware build that + * formatted the device - 0xf7 on the rk2705 build, 0xf1 on the Samsung + * YP-CP3 - as does the system offset. Neither is stored anywhere. Both are + * recovered from the structure at mount (probe_layout), and a device whose + * layout cannot be confirmed is mounted read-only: writing with the wrong + * base would erase blocks holding data. + * + * Bytes 0x100..0x10b are the SWAP LOG: one (old, new) position pair per + * exchange slot, recording the last swap that slot made. In the system + * zone's table, byte 0xff (unshifted) is the FORMAT FLAG - 2 for a formatted + * device - and the ten entries below the exchange record, table[base - 11 .. + * base - 2], are the device's REPLACEMENT BLOCKS for bad-block relocation. + * + * + * THE REMAP LOG + * + * A zone's table is never rewritten in place. Every version is appended, one + * sector per page, to the zone's remap-log block; the newest programmed page + * is the current table. The first page of a log block carries the block type + * 0x52 in its metadata, which is how a mount finds it. Every version is + * written twice, to the primary and to the mirror log block, so that + * recycling a full log - erasing both and starting again at page 0 - cannot + * lose the zone: an interrupted recycle leaves one of them intact, and the + * mount rewrites the other from it. + * + * + * THE EXCHANGE RECORD + * + * One block of the system zone holds device-wide state, one version per + * page: byte z of the newest page is zone z's BLOCK COUNT - how many logical + * blocks it holds - and from byte 0x40, ten little-endian 16-bit entries + * list the blocks that have been replaced after failing. The block counts + * give the capacity: every logical block is a super-block of data. + * + * + * WRITING + * + * NAND is erased a block at a time and programmed a page at a time, only + * once between erases. A write to a logical block therefore goes to a + * different, erased physical block - an EXCHANGE BLOCK - through one of six + * exchange slots per zone: + * + * 1. open: swap the logical block's table entry with the slot's pool + * entry, record the swap in the swap log, erase the new block, and + * publish the table - all before any data is written + * 2. copy forward the pages before the first one being written, from the + * old block; then write pages in order, the slot's cursor advancing + * 3. close: copy forward the rest of the old block + * + * The old block is now in the pool, to be erased when a later write needs + * it. A full exchange block is a finished write. Pages are staged in three + * page-sized RAM buffers, so sectors written one at a time reach the flash a + * page at a time; ftl_a_sync() writes them out. + * + * + * POWER LOSS + * + * There is no journal. Because the table is published before the data, a + * write cut short leaves a published table pointing at an exchange block + * that is not full, and a swap-log entry whose old and new differ. At every + * writable mount, recovery finds such slots, finds the last page programmed + * in the exchange block, and merges those pages with the rest of the old + * block into the zone's spare block, then publishes the table with the + * swap undone. Everything before the final publish can be repeated, so + * losing power during recovery only means recovering again. + * + * Whatever is still in the RAM buffers when power fails is lost. + * + * + * BAD BLOCKS + * + * A block is bad when metadata byte 0 of its first page is not 0xff. When a + * program or erase fails, the block is marked bad and replaced by the next + * of the ten replacement blocks, the replacement recorded in the exchange + * record. Blocks that are bad from the factory are simply never in a table. + * + * + * METADATA + * + * Of each sector's spare bytes the FTL uses three: + * + * [0] 0xff = good block (first page only) + * [1] 0x00 = this page is programmed - written by the flash layer on every + * program, so an erased page reads 0xff + * [2] block type: 0x52 remap-log block; 0x69 marks the boot area's ID + * blocks + */ + +#include +#include +#include + +#include "flash-rk27xx.h" +#include "ftl-scheme-a.h" + +/* ---- layout ---- */ + +#define ZONE_BLOCKS 256 /* blocks per zone */ +#define MAX_ZONES 64 +#define EXCH_SLOTS 6 /* exchange slots per zone */ +#define REPLACEMENT_SLOTS 10 /* bad-block replacements, device-wide */ +#define WRITE_CACHE_PAGES 3 /* page staging buffers */ + +#define TABLE_SIZE 512 +#define TBL_LOG_PRIMARY 0xff /* position of the primary log block */ +#define TBL_LOG_MIRROR 0xfe /* ... and of its mirror */ +#define TBL_SWAP_LOG 0x100 /* EXCH_SLOTS (old, new) pairs */ +#define TBL_FORMAT_FLAG 0xff /* system zone only, never shifted */ + +/* In the exchange record: the replaced-block list */ +#define XREC_REPLACED 0x40 + +/* The original firmware's reserve base, and the one used before a mount has + * derived the device's own */ +#define DEFAULT_RESERVE_BASE 0xf7 + +/* The format flag of a formatted device */ +#define FORMAT_FLAG 2 + +/* ---- metadata ---- */ + +#define META_BAD 0 /* [0]: 0xff = good block */ +#define META_PROGRAMMED 1 /* [1]: 0x00 once the page is programmed */ +#define META_TYPE 2 /* [2]: block type */ +#define META_ERASED 0xff +#define META_TYPE_LOG 0x52 + +static const uint8_t log_block_meta[FLASH_META_SIZE] = + { 0xff, 0xff, META_TYPE_LOG }; +static const uint8_t bad_block_meta[FLASH_META_SIZE] = + { 0x00, 0xff, 0xff }; + +#define NO_ZONE 0xffff +#define NO_REPLACEMENT 0xffff /* an unused replaced-block entry */ +#define NO_LBA 0xffffffff + +/* ---- state ---- */ + +/* An exchange slot: a logical block being rewritten into an exchange block. */ +struct exch_slot +{ + uint32_t cursor; /* next sector to write in the exchange block */ + uint32_t end; /* the sector after the exchange block */ + uint32_t src; /* first sector of the block being replaced */ + uint8_t age; /* EXCH_SLOTS - 1 = used last, 0 = reused next */ + bool open; +}; + +struct zone +{ + uint8_t log_block; /* position of the primary remap-log block */ + uint16_t log_page; /* page of the newest table version in it */ + uint8_t block_count; /* logical blocks in the zone */ + struct exch_slot exch[EXCH_SLOTS]; +}; + +/* A page being assembled in RAM. */ +struct write_cache +{ + uint8_t buf[FLASH_MAX_SEC_PER_PAGE * FLASH_SECTOR_SIZE]; + bool valid; + uint8_t age; /* as exch_slot.age */ + uint32_t next_lba; /* the page holds [page start, next_lba) */ + struct exch_slot *slot; /* the exchange slot the page is for */ +}; + +static struct +{ + const struct flash_geometry *geo; + struct ftl_a_config config; + bool mounted; + + /* the layout of this device */ + uint8_t reserve_base; + uint16_t boot_blocks; /* (system zone << 8) | system offset */ + uint32_t logical_sectors; /* both volumes */ + uint16_t replaced[REPLACEMENT_SLOTS]; + struct zone zones[MAX_ZONES]; + + /* what the last mount found */ + enum ftl_a_error error; + enum ftl_a_confidence confidence; + bool write_protect; + uint8_t base_fits; + uint8_t format_flag; + + /* the table of cached_zone, the one most recently used */ + uint16_t cached_zone; + uint8_t table[TABLE_SIZE]; + + struct write_cache cache[WRITE_CACHE_PAGES]; +} ftl = +{ + .reserve_base = DEFAULT_RESERVE_BASE, + .write_protect = true, +}; + +/* Scratch sector for the exchange record and for the system zone's table, + * shared by relocate_block() and replaced_block(); neither needs it across a + * call to the other. */ +static uint8_t scratch[TABLE_SIZE]; + +/* ---- geometry helpers ---- */ + +static uint8_t sec_per_page_raw(void) +{ + return ftl.geo->sec_per_page_raw; +} + +static uint8_t sec_per_page(void) +{ + return ftl.geo->sec_per_page; +} + +static uint16_t sec_per_block(void) +{ + return ftl.geo->sec_per_block; +} + +static uint8_t system_zone(void) +{ + return (uint8_t)(ftl.boot_blocks >> 8); +} + +static uint8_t system_offset(void) +{ + return (uint8_t)ftl.boot_blocks; +} + +/* How far the table indices of `zone` are shifted down by boot blocks. */ +static uint8_t table_shift(uint16_t zone) +{ + uint8_t shift = 0; + + if (zone <= system_zone()) + { + shift = system_offset(); + } + return shift; +} + +/* Table index of the exchange record (meaningful in the system zone). */ +static uint8_t xrec_index(void) +{ + return (uint8_t)(ftl.reserve_base - 1 - system_offset()); +} + +/* First sector of block `blk`, (zone << 8) | position. Past the end of the + * chip it returns the sector past the end, which the flash layer refuses. */ +static uint32_t block_sector(uint16_t blk) +{ + uint32_t sec = ftl.geo->total_sectors; + + if (blk < ftl.geo->total_blocks) + { + sec = (uint32_t)blk * sec_per_block(); + } + return sec; +} + +/* Sector of the system zone's current table. */ +static uint32_t system_table_sector(void) +{ + const struct zone *sz = &ftl.zones[system_zone()]; + + return block_sector((uint16_t)(sz->log_block + (system_zone() << 8))) + + (uint32_t)sec_per_page_raw() * sz->log_page; +} + +static void set_write_protect(bool protect) +{ + ftl.write_protect = protect; + flash_set_writable(!protect); +} + +static void reset_state(void) +{ + int i, j; + + for (i = 0; i < WRITE_CACHE_PAGES; i++) + { + ftl.cache[i].valid = false; + ftl.cache[i].age = (uint8_t)i; + ftl.cache[i].next_lba = NO_LBA; + } + + ftl.cached_zone = NO_ZONE; + + for (i = 0; i < MAX_ZONES; i++) + { + for (j = 0; j < EXCH_SLOTS; j++) + { + ftl.zones[i].exch[j].open = false; + ftl.zones[i].exch[j].age = (uint8_t)j; + } + } +} + +/* Sectors on a volume. USER is whatever SYS leaves. */ +static uint32_t volume_sectors(int volume) +{ + uint32_t sectors = 0; + + if (volume == FTL_A_VOL_SYS) + { + sectors = ftl.config.sys_sectors; + } + else if (volume == FTL_A_VOL_USER) + { + sectors = ftl.logical_sectors - ftl.config.sys_sectors; + } + return sectors; +} + +/* ---- logical addresses ---- */ + +/* Split a logical sector into its zone, logical block in the zone and sector + * in the block. Zones hold different numbers of logical blocks, so this + * walks the block counts. */ +static void locate(uint32_t lba, uint16_t *zone, uint8_t *block, + uint16_t *sector) +{ + uint32_t blk = lba / sec_per_block(); + uint16_t z; + + *sector = (uint16_t)(lba % sec_per_block()); + + for (z = 0; z < MAX_ZONES && blk >= ftl.zones[z].block_count; z++) + { + blk -= ftl.zones[z].block_count; + } + + *block = (uint8_t)blk; + *zone = z; +} + +/* First logical sector of `zone`. A zone past the last holding any blocks + * returns the capacity. */ +static uint32_t zone_first_lba(uint16_t zone) +{ + uint32_t lba = 0; + uint16_t z = 0; + + while (z != zone && z < MAX_ZONES && lba < ftl.logical_sectors) + { + lba += (uint32_t)ftl.zones[z].block_count * sec_per_block(); + z++; + } + return lba; +} + +/* ---- bad blocks ---- */ + +/* Is the block containing `sec` marked bad? On a two-plane part both planes' + * markers count - but, as the original firmware does, only the first read's + * status decides whether they are consulted at all. */ +static bool block_is_bad(uint32_t sec) +{ + uint32_t addr = sec - sec % sec_per_block(); + uint8_t meta[FLASH_META_SIZE]; + uint8_t marker = 1; + + if (flash_read(addr, NULL, meta, 1) == 0) + { + marker = meta[META_BAD]; + if (ftl.geo->planes > 1) + { + flash_read(addr + sec_per_page_raw(), NULL, meta, 1); + marker &= meta[META_BAD]; + } + } + return marker != META_ERASED; +} + +static void mark_block_bad(uint32_t sec) +{ + uint32_t row = sec - sec % sec_per_block(); + + flash_erase(row); + flash_program(row, NULL, bad_block_meta, 1); + + if (ftl.geo->planes > 1) + { + row += sec_per_page_raw(); + flash_program(row, NULL, bad_block_meta, 1); + } +} + +/* The block that `blk` has been replaced with, following replacements of + * replacements; `blk` itself when it has none. */ +static uint32_t replaced_block(uint32_t blk) +{ + uint32_t result = blk; + int i; + + for (i = 0; i < REPLACEMENT_SLOTS && ftl.replaced[i] != NO_REPLACEMENT; i++) + { + if (ftl.replaced[i] == (uint16_t)result) + { + flash_read(system_table_sector(), scratch, NULL, 1); + result = scratch[xrec_index() + i - REPLACEMENT_SLOTS] + + (system_zone() << 8); + } + } + return result; +} + +/* Replace the block containing `sec` after a program or erase failed in it, + * and return the sector in the replacement that corresponds to `sec`. + * + * With `data`, a write of `n` sectors at `sec` is carried over: the sectors + * of the block before `sec` are copied, then the data programmed. The + * replacement is recorded in the exchange record, any exchange slot writing + * into the failed block is moved to the replacement, and the failed block is + * marked bad. When every replacement is used up, `sec` itself is returned. + * + * The original firmware records replacements but, reading the exchange + * record back at mount, looks for them on a page it never writes them to: + * they are forgotten at every reboot. That is kept - see mount_scan(). */ +static uint32_t relocate_block(uint32_t sec, const void *data, uint16_t n) +{ + uint32_t result = sec; + uint32_t in_block = sec % sec_per_block(); + uint16_t failed_blk; + uint32_t xrec; + int slot; + + if (ftl.replaced[REPLACEMENT_SLOTS - 1] == NO_REPLACEMENT) + { + failed_blk = (uint16_t)((sec < ftl.geo->total_sectors) ? + sec / sec_per_block() : ftl.geo->total_blocks); + + flash_read(system_table_sector(), scratch, NULL, 1); + xrec = block_sector((uint16_t)(scratch[xrec_index()] + + (system_zone() << 8))); + + for (slot = 0; slot < REPLACEMENT_SLOTS; slot++) + { + uint32_t dest; + bool retargeted = false; + int z, k; + + if (ftl.replaced[slot] != NO_REPLACEMENT) + { + continue; + } + + dest = block_sector((uint16_t)((system_zone() << 8) + + scratch[xrec_index() + slot - REPLACEMENT_SLOTS])); + + if (data != NULL) + { + flash_copy(sec - in_block, dest, (uint16_t)in_block); + dest += in_block; + + if (n != 0) + { + if (ftl.geo->planes > 1 && n != sec_per_page_raw()) + { + flash_program_page(dest, data, NULL); + dest += sec_per_page(); + } + else + { + flash_program(dest, data, NULL, sec_per_page_raw()); + dest += sec_per_page_raw(); + } + } + } + + /* append the new list to the exchange record */ + flash_read(xrec + slot * sec_per_page_raw(), scratch, NULL, 1); + scratch[XREC_REPLACED + slot * 2] = (uint8_t)failed_blk; + scratch[XREC_REPLACED + slot * 2 + 1] = (uint8_t)(failed_blk >> 8); + flash_program(xrec + (slot + 1) * sec_per_page_raw(), scratch, + NULL, 1); + + /* an exchange slot writing into the failed block follows it */ + for (z = 0; z < MAX_ZONES && !retargeted; z++) + { + for (k = 0; k < EXCH_SLOTS && !retargeted; k++) + { + struct exch_slot *s = &ftl.zones[z].exch[k]; + uint32_t block_start = s->cursor & ~(sec_per_block() - 1); + + if (s->open && block_start <= sec && s->end > sec) + { + uint32_t dest_start = dest & ~(sec_per_block() - 1); + + s->cursor = dest_start + s->cursor % sec_per_block(); + s->end = dest_start + sec_per_block(); + retargeted = true; + } + } + } + + mark_block_bad(sec); + ftl.replaced[slot] = failed_blk; + result = dest; + break; + } + } + return result; +} + +/* ---- the zone table ---- */ + +/* Bring `zone`'s current table into ftl.table. */ +static void load_table(uint16_t zone) +{ + if (ftl.cached_zone != zone) + { + struct zone *z = &ftl.zones[zone]; + + ftl.cached_zone = zone; + flash_read(block_sector((uint16_t)((zone << 8) + z->log_block)) + + z->log_page * sec_per_page_raw(), + ftl.table, NULL, 1); + } +} + +/* Where logical sector `lba` is committed: its block per the table, ignoring + * any exchange block it is being rewritten into. */ +static uint32_t map_committed(uint32_t lba) +{ + uint16_t zone, sector; + uint8_t block; + uint32_t blk; + + locate(lba, &zone, &block, §or); + load_table(zone); + + blk = replaced_block((zone << 8) + ftl.table[block]); + return block_sector((uint16_t)blk) | sector; +} + +/* Where logical sector `lba` can be read now. A sector in a block being + * rewritten reads from the block being replaced until the exchange block's + * cursor has passed it; past the cursor the exchange block is erased. */ +static uint32_t map_current(uint32_t lba) +{ + uint16_t zone, sector; + uint8_t block; + uint32_t addr; + int i; + + locate(lba, &zone, &block, §or); + load_table(zone); + + addr = replaced_block((zone << 8) + ftl.table[block]); + addr = block_sector((uint16_t)addr) | sector; + + for (i = 0; i < EXCH_SLOTS; i++) + { + struct exch_slot *s = &ftl.zones[zone].exch[i]; + + if (s->open && s->cursor <= addr && s->end > addr) + { + addr = s->src + addr % sec_per_block(); + break; + } + } + return addr; +} + +/* Append ftl.table as the new version of the table of the zone holding + * `lba`, to both log blocks. A full log is erased and restarted. */ +static void publish_table(uint32_t lba) +{ + uint16_t zone, sector; + uint8_t block; + struct zone *z; + uint32_t primary, mirror; + + locate(lba, &zone, &block, §or); + z = &ftl.zones[zone]; + + ftl.cached_zone = zone; + z->log_page++; + + mirror = (zone << 8) + + ftl.table[(uint8_t)(TBL_LOG_MIRROR - table_shift(zone))]; + primary = replaced_block(z->log_block + (zone << 8)); + mirror = replaced_block(mirror); + + primary = block_sector((uint16_t)primary); + mirror = block_sector((uint16_t)mirror); + + if (z->log_page < sec_per_block() / sec_per_page_raw()) + { + uint32_t page = z->log_page * sec_per_page_raw(); + + flash_program(page + primary, ftl.table, NULL, 1); + flash_program(page + mirror, ftl.table, NULL, 1); + } + else + { + z->log_page = 0; + + if (flash_erase(primary) != 0) + { + primary = relocate_block(primary, NULL, 0); + } + flash_program(primary, ftl.table, log_block_meta, 1); + + if (flash_erase(mirror) != 0) + { + mirror = relocate_block(mirror, NULL, 0); + } + flash_program(mirror, ftl.table, log_block_meta, 1); + } +} + +/* ---- exchange slots ---- */ + +/* Finish the rewrite a slot is doing: copy the rest of the old block into + * the exchange block, and free the slot. */ +static void exch_close(struct exch_slot *s) +{ + if (s->open) + { + if (s->cursor != s->end) + { + uint32_t from = s->src + s->cursor % sec_per_block(); + uint16_t count = (uint16_t)(s->end - s->cursor); + + if (flash_copy(from, s->cursor, count) != 0) + { + uint32_t dest = relocate_block(s->cursor, NULL, 0); + + if (dest != s->cursor) + { + flash_copy(from, dest, count); + } + } + } + s->open = false; + } +} + +/* Start rewriting the logical block holding `lba`: close any slot already + * writing into its current block, take the least recently used slot, swap + * the slot's pool block in, erase it, and publish the table. Returns the + * slot's index. + * + * The slot ages form a permutation of 0 .. EXCH_SLOTS-1, so there always is + * a slot of age 0 to take. */ +static int exch_open(uint32_t lba) +{ + uint16_t zone, sector; + uint8_t block, pool, fresh; + struct exch_slot *s = NULL; + uint32_t addr; + int i; + + locate(lba, &zone, &block, §or); + + for (i = 0; i < EXCH_SLOTS; i++) + { + s = &ftl.zones[zone].exch[i]; + + if (s->open) + { + addr = map_committed(lba) & ~(sec_per_block() - 1); + if ((s->cursor & ~(sec_per_block() - 1)) == addr) + { + exch_close(s); + } + } + } + + for (i = 0; i < EXCH_SLOTS; i++) + { + s = &ftl.zones[zone].exch[i]; + + if (!s->open || s->age == 0) + { + exch_close(s); + break; + } + } + + /* swap the pool block in; the swap log records (old, new) */ + pool = (uint8_t)(ftl.reserve_base - table_shift(zone) + i); + fresh = ftl.table[pool]; + ftl.table[pool] = ftl.table[block]; + ftl.table[TBL_SWAP_LOG + i * 2] = ftl.table[block]; + ftl.table[TBL_SWAP_LOG + i * 2 + 1] = fresh; + ftl.table[block] = fresh; + + addr = replaced_block((zone << 8) + fresh); + addr = block_sector((uint16_t)addr); + + if (flash_erase(addr) != 0) + { + addr = relocate_block(addr, NULL, 0); + } + + s->cursor = addr; + s->end = addr + sec_per_block(); + s->open = true; + + publish_table(lba); + + return i; +} + +/* The exchange slot sector `lba` - the first of a page - is written through, + * opening one if needed. Pages are written in order: if the slot's cursor is + * behind `lba`, the pages in between are copied forward from the old block + * first. */ +static struct exch_slot *exch_for_write(uint32_t lba) +{ + uint16_t zone, sector; + uint8_t block; + uint32_t addr, in_block; + struct exch_slot *s = NULL; + int i; + + locate(lba, &zone, &block, §or); + addr = map_committed(lba); + + for (i = 0; i < EXCH_SLOTS; i++) + { + s = &ftl.zones[zone].exch[i]; + + if (!s->open || s->cursor > addr || s->end <= addr) + { + continue; + } + + if (s->cursor != addr) + { + uint16_t count = (uint16_t)(addr - s->cursor); + + in_block = s->cursor % sec_per_block(); + if (flash_copy(s->src + in_block, s->cursor, count) != 0) + { + uint32_t dest = relocate_block(s->cursor, NULL, 0); + + if (dest != s->cursor) + { + flash_copy(s->src + in_block, dest, count); + addr = addr - s->cursor + dest; + } + } + s->cursor = addr; + } + break; + } + + if (i >= EXCH_SLOTS) + { + i = exch_open(lba); + s = &ftl.zones[zone].exch[i]; + + in_block = addr % sec_per_block(); + s->src = addr - in_block; + + if (in_block != 0) + { + if (flash_copy(s->src, s->cursor, (uint16_t)in_block) != 0) + { + uint32_t dest = relocate_block(s->cursor, NULL, 0); + + if (dest != s->cursor) + { + flash_copy(s->src, dest, (uint16_t)in_block); + s->cursor = dest; + } + } + } + + s->cursor += in_block; + } + + for (i = 0; i < EXCH_SLOTS; i++) + { + struct exch_slot *other = &ftl.zones[zone].exch[i]; + + if (other->age > s->age) + { + other->age--; + } + } + s->age = EXCH_SLOTS - 1; + + return s; +} + +/* ---- power-loss recovery ---- */ + +/* The number of sectors of an exchange block that hold data: everything up + * to the last programmed page. A last page that does not read back cleanly + * was cut short by the power loss and is not counted. + * + * That back-off computes page & ~sec_per_page, clearing a single bit, where + * aligning down to a page would need page & ~(sec_per_page - 1). It is the + * original firmware's arithmetic, kept so that recovery merges exactly the + * pages the original firmware would. */ +static int32_t exch_written(uint32_t src) +{ + uint8_t meta[FLASH_META_SIZE]; + int32_t page = (int16_t)(sec_per_block() - sec_per_page_raw()); + bool done = false; + + while (!done) + { + flash_read(src + page, NULL, meta, 1); + + if (meta[META_PROGRAMMED] != 0) + { + page = (int16_t)(page - sec_per_page_raw()); + done = page < 0; + } + else + { + if (flash_read(src + page, NULL, NULL, sec_per_page_raw()) != 0) + { + page = (int16_t)((page & ~sec_per_page()) - sec_per_page_raw()); + } + done = true; + } + } + + return (int16_t)(page + sec_per_page_raw()); +} + +/* Finish, in every zone, the writes a power loss interrupted - see POWER + * LOSS above. For each swap-log entry still open, the written pages of the + * exchange block and the rest of the old block are merged into the zone's + * spare block, which takes the logical block's place; the exchange block + * returns to the pool and the old block becomes the new spare. */ +static void recover_interrupted(void) +{ + uint32_t capacity = volume_sectors(FTL_A_VOL_SYS) + + volume_sectors(FTL_A_VOL_USER); + uint32_t zone_blk = 0; + bool done = false; + + while (!done && zone_blk < ftl.geo->total_blocks) + { + uint16_t zone = (uint16_t)(zone_blk >> 8); + int slot; + + if (zone_blk + ZONE_BLOCKS <= ftl.boot_blocks) + { + zone_blk += ZONE_BLOCKS; /* all boot area */ + continue; + } + + if (zone_first_lba(zone) >= capacity) + { + done = true; /* no zone after this holds data */ + continue; + } + + map_current(zone_first_lba(zone)); /* loads the zone's table */ + + for (slot = 0; slot < EXCH_SLOTS; slot++) + { + uint8_t old = ftl.table[TBL_SWAP_LOG + slot * 2]; + uint8_t new = ftl.table[TBL_SWAP_LOG + slot * 2 + 1]; + uint8_t pool, spare, spare_pos; + uint32_t exch, dest, orig, n; + int32_t written; + uint8_t at; + + if (old == new) + { + continue; /* closed */ + } + + exch = block_sector((uint16_t)replaced_block(zone_blk + new)); + written = exch_written(exch); + + if (sec_per_block() <= written) + { + continue; /* full: the write completed */ + } + + spare = (uint8_t)(ftl.reserve_base + EXCH_SLOTS + - table_shift(zone)); + pool = (uint8_t)(ftl.reserve_base - table_shift(zone)); + + /* undo the swap: the spare block takes the logical block's + * place, the exchange block goes back to the pool, the old + * block becomes the spare */ + spare_pos = ftl.table[spare]; + for (at = 0; at < pool && ftl.table[at] != new; at++) + { + } + ftl.table[pool + slot] = ftl.table[at]; + ftl.table[at] = spare_pos; + ftl.table[spare] = old; + + ftl.table[TBL_SWAP_LOG + slot * 2] = 0xff; + ftl.table[TBL_SWAP_LOG + slot * 2 + 1] = 0xff; + + dest = block_sector((uint16_t)replaced_block(zone_blk + spare_pos)); + orig = block_sector((uint16_t)replaced_block(zone_blk + old)); + + flash_erase(dest); + + /* the written pages, where they read back; the old block's + * where they do not */ + for (n = 0; n < (uint32_t)written; n += sec_per_page_raw()) + { + if (flash_read(exch + n, NULL, NULL, sec_per_page_raw()) != 0) + { + flash_copy(orig + n, dest + n, sec_per_page_raw()); + } + else + { + flash_copy(exch + n, dest + n, sec_per_page_raw()); + } + } + flash_copy(orig + written, dest + written, + (uint16_t)(sec_per_block() - written)); + + publish_table(zone_first_lba(zone)); + } + + zone_blk += ZONE_BLOCKS; + } +} + +/* ---- recognising the layout ---- + * + * The reserve base and the system offset differ between the firmware builds + * that format devices, and neither is recorded. Both follow from the + * structure: + * + * system offset the two log blocks are always the top two table entries, + * shifted down by the offset in the system zone - so the + * offset is 0xff less the index of the primary log block's + * own position in its table + * base opening a slot i parks the displaced block at + * table[base + i] and records it in the swap log, so a used + * slot gives base = index of its old block - i; and, needing + * no write history, the system zone's table[base - 1] must + * name an exchange record whose block counts are all at + * most base, peaking at exactly base + * + * The second test decides, the first is only a hint. Only a unique answer, + * cross-checked against every zone's log blocks, makes the device writable: + * a wrong base or offset would publish tables that erase live data. */ + +/* Find a zone's log blocks - metadata type 0x52 - and the newest programmed + * page of the first. Returns how many were found: 2 normally, 1 when a + * power loss during a log recycle left one erased, 0 for none. */ +static int probe_find_log(uint16_t zone_base, uint8_t *pos_a, uint8_t *pos_b, + uint16_t *newest) +{ + uint8_t meta[FLASH_META_SIZE]; + uint16_t pages = sec_per_block() / sec_per_page_raw(); + int pos, i, found = 0; + + *pos_a = 0xff; + *pos_b = 0xff; + *newest = 0; + + for (pos = 0xff; pos >= 0 && found < 2; pos--) + { + uint32_t sec = block_sector((uint16_t)(zone_base + pos)); + + if (flash_read(sec, NULL, meta, 1) == 0 + && meta[META_TYPE] == META_TYPE_LOG) + { + if (found == 0) + { + *pos_a = (uint8_t)pos; + } + else + { + *pos_b = (uint8_t)pos; + } + found++; + } + } + + if (found > 0) + { + uint32_t base = block_sector((uint16_t)(zone_base + *pos_a)); + + for (i = 0; i < (int)pages; i++) + { + if (flash_read(base + (uint32_t)i * sec_per_page_raw(), + NULL, meta, 1) != 0) + { + break; + } + if (meta[META_PROGRAMMED] != META_ERASED) + { + *newest = (uint16_t)i; + } + } + } + return found; +} + +/* The base the swap log points to. -1 with no evidence, -2 if slots + * disagree. Only values that occur once in the table count: a formatted + * system zone repeats some. */ +static int probe_base(const uint8_t *tbl, int *votes) +{ + int i, j, base = -1; + bool conflict = false; + + *votes = 0; + for (i = 0; i < EXCH_SLOTS && !conflict; i++) + { + uint8_t old = tbl[TBL_SWAP_LOG + i * 2]; + uint8_t new = tbl[TBL_SWAP_LOG + i * 2 + 1]; + int at = -1, count = 0; + + if (old == new) + { + continue; /* unused slot */ + } + + for (j = 0; j < ZONE_BLOCKS; j++) + { + if (tbl[j] == old) + { + count++; + at = j; + } + } + if (count != 1 || at - i < 0) + { + continue; + } + + if (base >= 0 && base != at - i) + { + conflict = true; + } + else + { + base = at - i; + (*votes)++; + } + } + + if (conflict) + { + base = -2; + } + else if (*votes == 0) + { + base = -1; + } + return base; +} + +/* The system offset from where the log blocks sit. -1 if they do not sit as + * the top two entries of some shift. */ +static int probe_sys_offset(const uint8_t *tbl, uint8_t log_primary, + uint8_t log_mirror) +{ + int i, at = -1, count = 0, offset = -1; + + for (i = 0; i < ZONE_BLOCKS; i++) + { + if (tbl[i] == log_primary) + { + count++; + at = i; + } + } + if (count == 1 && at >= 1 && tbl[at - 1] == log_mirror) + { + offset = TBL_LOG_PRIMARY - at; + } + return offset; +} + +/* Does `base` make the system zone's exchange record a real one? Its block + * counts must all be at most base, and reach it: + * + * CP3 d9 f1 f1 f1 f1 ... f1 base f1 + * rk2705 df f6 f6 f7 f6 f7 ... f7 f4 base f7 + * + * A wrong base names some other block, whose bytes are not that. + * The newest page is found as mount_scan() finds it, including ignoring the + * read status: erased pages fail ECC, and only byte 0 decides. */ +static bool probe_base_fits(uint8_t base, const uint8_t *sys_tbl, + uint8_t sys_zone, uint8_t sys_offset, + uint16_t nzones, uint8_t *page) +{ + uint32_t addr; + int idx, z, max = -1; + bool fits = true; + + addr = block_sector((uint16_t)(sys_tbl[(uint8_t)(base - 1 - sys_offset)] + + (sys_zone << 8))); + + for (idx = REPLACEMENT_SLOTS - 1; ; idx--) + { + flash_read(addr + (uint32_t)idx * sec_per_page_raw(), page, NULL, 1); + if (page[0] != META_ERASED || idx == 0) + { + break; + } + } + + for (z = 0; z < nzones && fits; z++) + { + if (page[z] > base) + { + fits = false; + } + else if (page[z] > max) + { + max = page[z]; + } + } + return fits && max == base; +} + +/* Read zone z's table version `newest` from log block `log_blk`. */ +static int probe_read_table(uint16_t z, uint8_t log_blk, uint16_t newest, + uint8_t *tbl) +{ + return flash_read(block_sector((uint16_t)((z << 8) + log_blk)) + + (uint32_t)newest * sec_per_page_raw(), + tbl, NULL, 1); +} + +/* With one log block left, could the system offset be `so`? The top `so` + * entries of the system zone's table are the boot blocks, recorded as + * positions below `so` (formatting zeroes them, so they repeat), and the + * lost log block's position cannot be one of them. */ +static bool probe_layout_fits(const uint8_t *tbl, int so, + bool survivor_is_primary) +{ + int j; + bool fits = true; + uint8_t other; + + for (j = ZONE_BLOCKS - so; j <= TBL_LOG_PRIMARY; j++) + { + if (tbl[j] >= so) + { + fits = false; + } + } + + other = survivor_is_primary ? tbl[TBL_LOG_MIRROR - so] + : tbl[TBL_LOG_PRIMARY - so]; + return fits && other >= so; +} + +/* How many bases fit if the system zone is `sz` with offset `so`; *found + * gets the last that did. The pool and the spare must fit under the log + * blocks, and table[base - 1 - so] must be a real entry. */ +static int probe_base_search(const uint8_t *sys_tbl, uint8_t sz, uint8_t so, + uint16_t nzones, int *found) +{ + static uint8_t page[TABLE_SIZE]; + int b, fits = 0; + + for (b = so + 1; b + EXCH_SLOTS < TBL_LOG_MIRROR; b++) + { + if (probe_base_fits((uint8_t)b, sys_tbl, sz, so, nzones, page)) + { + fits++; + *found = b; + } + } + return fits; +} + +/* Recognise the layout of `nzones` zones - see probe_layout(). */ +static bool probe_zones(uint16_t nzones) +{ + static uint8_t tbl[TABLE_SIZE]; + uint8_t log_a[MAX_ZONES], log_b[MAX_ZONES]; + uint8_t nlog[MAX_ZONES]; /* log blocks found: 0, 1 or 2 */ + uint16_t newest[MAX_ZONES]; + int16_t zbase[MAX_ZONES]; + int16_t zoffset[MAX_ZONES]; + int16_t zcand[MAX_ZONES][2]; /* one-log zones: offset candidates */ + uint16_t z; + int base_hi = -1, sys_zone = -1, i, confirm = 0, disagree = 0; + int found = -1; + bool usable = false; + + for (z = 0; z < nzones; z++) + { + int votes; + + zbase[z] = -1; + zoffset[z] = -1; + zcand[z][0] = zcand[z][1] = -1; + + nlog[z] = (uint8_t)probe_find_log((uint16_t)(z << 8), &log_a[z], + &log_b[z], &newest[z]); + if (nlog[z] == 0) + { + continue; + } + if (probe_read_table(z, log_a[z], newest[z], tbl) != 0) + { + nlog[z] = 0; + continue; + } + + zbase[z] = (int16_t)probe_base(tbl, &votes); + + if (nlog[z] == 2) + { + zoffset[z] = (int16_t)probe_sys_offset(tbl, log_a[z], log_b[z]); + } + else + { + /* One survivor: the primary at table[0xff - offset] or the + * mirror at table[0xfe - offset]; settled below. */ + int j, at = -1, count = 0; + + for (j = 0; j < ZONE_BLOCKS; j++) + { + if (tbl[j] == log_a[z]) + { + count++; + at = j; + } + } + if (count == 1) + { + zcand[z][0] = (int16_t)(TBL_LOG_PRIMARY - at); + zcand[z][1] = (int16_t)((at <= TBL_LOG_MIRROR) + ? TBL_LOG_MIRROR - at : -1); + } + } + + if (zbase[z] >= 0 && zbase[z] > base_hi) + { + base_hi = zbase[z]; + } + } + + /* the offset is only visible in the system zone; elsewhere it is 0 */ + for (z = 0; z < nzones; z++) + { + if (zoffset[z] > 0 && (int)z > sys_zone) + { + sys_zone = (int)z; + } + } + + if (sys_zone >= 0) + { + if (probe_read_table((uint16_t)sys_zone, log_a[sys_zone], + newest[sys_zone], tbl) == 0) + { + ftl.base_fits = (uint8_t)probe_base_search( + tbl, (uint8_t)sys_zone, (uint8_t)zoffset[sys_zone], + nzones, &found); + } + } + else + { + /* No zone shows an offset: it is 0, or the system zone is one that + * lost a log block. Try each one-log candidate; only a unique + * winner counts. */ + int wins = 0, win_z = -1, win_so = -1, win_base = -1; + + for (z = 0; z < nzones; z++) + { + for (i = 0; i < 2; i++) + { + int f = -1, so = zcand[z][i]; + + if (nlog[z] != 1 || so <= 0) + { + continue; + } + if (probe_read_table(z, log_a[z], newest[z], tbl) != 0) + { + continue; + } + if (!probe_layout_fits(tbl, so, i == 0)) + { + continue; + } + if (probe_base_search(tbl, (uint8_t)z, (uint8_t)so, + nzones, &f) == 1) + { + wins++; + win_z = z; + win_so = so; + win_base = f; + } + } + } + + if (wins == 1) + { + sys_zone = win_z; + zoffset[win_z] = (int16_t)win_so; + ftl.base_fits = 1; + found = win_base; + } + else if (wins == 0 && nlog[0] && + probe_read_table(0, log_a[0], newest[0], tbl) == 0) + { + ftl.base_fits = (uint8_t)probe_base_search(tbl, 0, 0, nzones, + &found); + } + } + + if (sys_zone < 0) + { + ftl.boot_blocks = 0; + } + else + { + ftl.boot_blocks = (uint16_t)((sys_zone << 8) + | (uint8_t)zoffset[sys_zone]); + } + + if (ftl.base_fits == 1) + { + ftl.reserve_base = (uint8_t)found; + usable = true; + } + else if (base_hi >= 0) + { + ftl.reserve_base = (uint8_t)base_hi; /* unconfirmed: read-only */ + usable = true; + } + + if (usable) + { + /* Cross-check: every zone's log blocks must sit at the top two + * entries, as shifted. A zone that lost one confirms if the + * survivor sits at either. */ + for (z = 0; z < nzones; z++) + { + uint8_t shift; + bool ok; + + if (nlog[z] == 0 + || (nlog[z] == 2 && zbase[z] < 0 && zoffset[z] < 0)) + { + continue; + } + shift = (z <= (ftl.boot_blocks >> 8)) ? (uint8_t)ftl.boot_blocks + : 0; + if (probe_read_table(z, log_a[z], newest[z], tbl) != 0) + { + continue; + } + if (nlog[z] == 2) + { + ok = tbl[TBL_LOG_PRIMARY - shift] == log_a[z] && + tbl[TBL_LOG_MIRROR - shift] == log_b[z]; + } + else + { + ok = tbl[TBL_LOG_PRIMARY - shift] == log_a[z] || + tbl[TBL_LOG_MIRROR - shift] == log_a[z]; + } + + if (ok) + { + confirm++; + } + else + { + disagree++; + } + } + + /* an offset of 0 is only measured if the system zone itself voted */ + if (disagree || confirm == 0 || ftl.base_fits != 1 || + (sys_zone < 0 && zoffset[0] < 0)) + { + ftl.confidence = FTL_A_CONF_ASSUMED; + } + else + { + ftl.confidence = FTL_A_CONF_CONFIRMED; + set_write_protect(ftl.config.read_only); + } + } + + return usable; +} + +/* Recognise the layout. Returns true if the device is usable - read-only + * unless the layout was confirmed (ftl.confidence). */ +static bool probe_layout(void) +{ + uint16_t nzones = (uint16_t)(ftl.geo->total_blocks >> 8); + bool usable = false; + + ftl.confidence = FTL_A_CONF_NONE; + set_write_protect(true); + ftl.base_fits = 0; + + if (nzones > 0 && nzones <= MAX_ZONES) + { + usable = probe_zones(nzones); + } + return usable; +} + +/* ---- mounting ---- */ + +/* Find the newest table version in log block `sec`: the last programmed + * page, by binary search on the "programmed" metadata byte. A page whose + * neighbour below is programmed but unreadable is taken as cut short, and + * the search settles one lower. Returns the page count, 0 if nothing was + * found. */ +static uint32_t log_versions(uint32_t sec, uint8_t *buf) +{ + uint32_t pages = sec_per_block() / sec_per_page_raw(); + uint32_t lo = 0, hi = pages - 1, mid, count = 0; + bool done = false; + + while (lo <= hi && !done) + { + mid = (lo + hi) / 2; + flash_read(sec + mid * sec_per_page_raw(), NULL, buf, 1); + + if (buf[META_PROGRAMMED] != META_ERASED) + { + if (mid == pages - 1) + { + count = mid + 1; + done = true; + } + else + { + lo = mid + 1; + } + } + else if (mid == 0) + { + count = 1; + done = true; + } + else + { + uint32_t below = mid - 1; + int rc; + + hi = below; + rc = flash_read(sec + below * sec_per_page_raw(), NULL, buf, 1); + if (buf[META_PROGRAMMED] != META_ERASED) + { + count = (rc == 0) ? mid : mid - 1; + done = true; + } + } + } + return count; +} + +/* Load every zone's log position, repairing log mirrors that disagree, then + * the device state from the system zone's table and the exchange record. + * Runs after probe_layout(). */ +static enum ftl_a_error mount_scan(void) +{ + static uint8_t buf[TABLE_SIZE]; + enum ftl_a_error err = FTL_A_OK; + uint8_t off_primary = (uint8_t)(TBL_LOG_PRIMARY - system_offset()); + uint8_t off_mirror = (uint8_t)(TBL_LOG_MIRROR - system_offset()); + uint32_t zone_blk; + uint32_t addr; + int i; + + for (zone_blk = 0; zone_blk < ftl.geo->total_blocks && err == FTL_A_OK; + zone_blk += ZONE_BLOCKS) + { + uint8_t log_pos[2] = { 0xff, 0xff }; + uint8_t log_page[2] = { 0xff, 0xff }; + uint8_t found = 0; + int pos = 0xff; + uint16_t zone = (uint16_t)(zone_blk >> 8); + struct zone *z; + + if (zone_blk + ZONE_BLOCKS <= ftl.boot_blocks) + { + continue; /* all boot area */ + } + + /* the log blocks, from the top; positions 0-2 are never searched */ + while (pos > 2) + { + uint32_t sec = block_sector((uint16_t)(zone_blk + pos)); + + if (!block_is_bad(sec)) + { + flash_read(sec, NULL, buf, 1); + if (buf[META_TYPE] == META_TYPE_LOG) + { + log_pos[found] = (uint8_t)pos; + log_page[found] = (uint8_t)(log_versions(sec, buf) - 1); + found++; + if (found >= 2) + { + break; + } + } + } + pos--; + } + + if (pos <= 2 && found == 0) + { + err = FTL_A_ERR_NO_LOG_BLOCK; + continue; + } + + z = &ftl.zones[zone]; + z->log_block = log_pos[0]; + + if (log_page[0] == log_page[1]) + { + z->log_page = log_page[0]; + continue; + } + + /* The two logs disagree: a power loss during an append or a + * recycle. Rewrite both from the newest version. With one log left, + * where the two belong comes from that table. */ + if (log_pos[1] == 0xff) + { + addr = block_sector((uint16_t)(zone_blk + log_pos[0])) + + log_page[0] * sec_per_page_raw(); + } + else + { + addr = block_sector((uint16_t)(zone_blk + log_pos[1])) + + log_page[1] * sec_per_page_raw(); + } + flash_read(addr, buf, NULL, 1); + + if (log_pos[1] == 0xff) + { + if (zone <= system_zone()) + { + log_pos[0] = buf[off_primary]; + log_pos[1] = buf[off_mirror]; + } + else + { + log_pos[0] = buf[TBL_LOG_PRIMARY]; + log_pos[1] = buf[TBL_LOG_MIRROR]; + } + z->log_block = log_pos[0]; + } + + /* A read-only mount cannot repair, and half a repair is worse than + * none - a refused erase leaves log_page pointing at a page never + * written, and every lookup in the zone reads garbage. */ + if (ftl.write_protect) + { + err = FTL_A_ERR_NEEDS_REPAIR; + continue; + } + + addr = block_sector((uint16_t)(zone_blk + log_pos[0])); + if (flash_erase(addr) != 0) + { + addr = relocate_block(addr, NULL, 0); + } + flash_program(addr, buf, log_block_meta, 1); + + addr = block_sector((uint16_t)(zone_blk + log_pos[1])); + if (flash_erase(addr) != 0) + { + addr = relocate_block(addr, NULL, 0); + } + flash_program(addr, buf, log_block_meta, 1); + + z->log_page = 0; + } + + if (err == FTL_A_OK) + { + uint32_t idx = REPLACEMENT_SLOTS - 1; + uint32_t nzones = ftl.geo->total_blocks >> 8; + uint32_t z; + + flash_read(system_table_sector(), buf, NULL, 1); + ftl.format_flag = buf[TBL_FORMAT_FLAG]; + + /* The exchange record's newest page is searched for from page 9 + * down, by its byte 0 - which relocate_block() never writes; so it + * always settles on page 0, and the replaced-block list is always + * the one formatting left, i.e. empty. As in the original firmware: + * a replaced block is found bad again rather than remembered. */ + addr = block_sector((uint16_t)(buf[xrec_index()] + + (system_zone() << 8))); + for (;;) + { + flash_read(addr + idx * sec_per_page_raw(), buf, NULL, 1); + if (buf[0] != META_ERASED || idx == 0) + { + break; + } + idx--; + } + + for (i = 0; i < REPLACEMENT_SLOTS; i++) + { + ftl.replaced[i] = (uint16_t)(buf[XREC_REPLACED + i * 2] | + (buf[XREC_REPLACED + i * 2 + 1] << 8)); + } + + for (i = 0; i < MAX_ZONES; i++) + { + ftl.zones[i].block_count = buf[i]; + } + + ftl.logical_sectors = 0; + for (z = 0; z < nzones && z < MAX_ZONES; z++) + { + ftl.logical_sectors += ftl.zones[z].block_count; + } + ftl.logical_sectors *= sec_per_block(); + + if (ftl.logical_sectors == 0) + { + err = FTL_A_ERR_ZERO_CAPACITY; + } + else + { + /* A count of 0xff is an erased page, not a zone: every zone + * keeps at least its log blocks and pool out of its 256. */ + for (z = 0; z < nzones && z < MAX_ZONES; z++) + { + if (ftl.zones[z].block_count == 0xff) + { + err = FTL_A_ERR_COUNTS_ERASED; + } + } + } + } + + return err; +} + +/* ---- reading and writing ---- */ + +static int read_sectors(int volume, uint32_t lba, uint8_t *p, uint16_t count) +{ + int ret = 0; + + if (volume == FTL_A_VOL_USER) + { + lba += volume_sectors(FTL_A_VOL_SYS); + } + + while (count) + { + uint32_t addr = map_current(lba); + struct write_cache *hit = NULL; + uint16_t len; + int i; + + /* a page being assembled holds the newest copy of what it holds */ + for (i = 0; i < WRITE_CACHE_PAGES; i++) + { + struct write_cache *c = &ftl.cache[i]; + + uint32_t held = c->next_lba & ~(uint32_t)(sec_per_page() - 1); + + if (c->valid && held <= lba && c->next_lba > lba) + { + hit = c; + break; + } + } + + if (hit == NULL) + { + uint16_t in_page = (uint16_t)(lba % sec_per_page_raw()); + + len = sec_per_page_raw() - in_page; + if (len > count) + { + len = count; + } + if (flash_read(addr, p, NULL, len) != 0) + { + ret = 1; + } + } + else + { + uint16_t in_page = (uint16_t)(lba % sec_per_page()); + + len = (uint16_t)(hit->next_lba % sec_per_page() - in_page); + if (len > count) + { + len = count; + } + memcpy(p, hit->buf + in_page * FLASH_SECTOR_SIZE, + (size_t)len * FLASH_SECTOR_SIZE); + } + + p += (uint32_t)len * FLASH_SECTOR_SIZE; + count -= len; + lba += len; + } + + return ret; +} + +/* Write a cached page out: complete it from the block being replaced, and + * program it at its exchange slot's cursor. A page that holds no sector yet + * is dropped. */ +static void cache_flush(struct write_cache *c) +{ + if (c->valid) + { + uint32_t lba = c->next_lba; + uint32_t addr = map_committed(lba); + uint8_t spp = sec_per_page(), sppr = sec_per_page_raw(); + uint16_t in_page = (uint16_t)(lba & (spp - 1)); + + if (in_page != 0) + { + uint32_t page_base = (lba % sec_per_block()) & ~(uint32_t)(spp - 1); + struct exch_slot *s = c->slot; + + /* the slot may have moved on since; take the current one. The + * original firmware does not store it back - neither does this, + * the page is done with it. */ + if ((addr & ~(uint32_t)(spp - 1)) != s->cursor) + { + s = exch_for_write(lba & ~(uint32_t)(spp - 1)); + } + + if (in_page <= sppr && ftl.geo->planes > 1) + { + flash_read(s->src + page_base + in_page, + c->buf + (uint32_t)in_page * FLASH_SECTOR_SIZE, NULL, + (uint8_t)(sppr - in_page)); + flash_read(s->src + page_base + sppr, + c->buf + (uint32_t)sppr * FLASH_SECTOR_SIZE, + NULL, sppr); + } + else + { + flash_read(s->src + page_base + in_page, + c->buf + (uint32_t)in_page * FLASH_SECTOR_SIZE, NULL, + (uint8_t)(spp - in_page)); + } + + flash_program_page(s->cursor, c->buf, NULL); + s->cursor += spp; + } + c->valid = false; + } +} + +static void cache_flush_all(void) +{ + int i; + + for (i = 0; i < WRITE_CACHE_PAGES; i++) + { + cache_flush(&ftl.cache[i]); + } +} + +/* Stage sectors into a page buffer; a completed page goes to its exchange + * slot. A buffer is taken - in this order - from one already holding the + * start of the same page (flushed first unless it ends right where this + * write begins), from the free ones, or from the least recently used. */ +static int write_sectors(int volume, uint32_t lba, const uint8_t *p, + uint16_t count) +{ + struct write_cache *slot = NULL; + uint32_t page_mask = sec_per_page() - 1; + uint32_t page_lba; + int i; + + if (volume == FTL_A_VOL_USER) + { + lba += volume_sectors(FTL_A_VOL_SYS); + } + + /* The original firmware resolves the first sector here, before anything + * else - it wants the chip it is on. Kept: it loads the zone's table, + * and the flash access of this driver was validated operation for + * operation against the original's. */ + map_current(lba); + + page_lba = lba & ~page_mask; + for (i = 0; i < WRITE_CACHE_PAGES && slot == NULL; i++) + { + struct write_cache *c = &ftl.cache[i]; + + if (c->valid && (c->next_lba & ~page_mask) == page_lba) + { + if (c->next_lba != lba) + { + cache_flush(c); + } + slot = c; + } + } + + for (i = 0; i < WRITE_CACHE_PAGES && slot == NULL; i++) + { + if (!ftl.cache[i].valid) + { + slot = &ftl.cache[i]; + } + } + + for (i = 0; i < WRITE_CACHE_PAGES && slot == NULL; i++) + { + if (ftl.cache[i].age == 0) + { + cache_flush(&ftl.cache[i]); + slot = &ftl.cache[i]; + } + } + + /* The ages form a permutation, so the least recently used buffer - age + * 0 - always exists. They have to be updated whichever way the buffer + * was chosen for that to hold. */ + for (i = 0; i < WRITE_CACHE_PAGES; i++) + { + if (ftl.cache[i].age > slot->age) + { + ftl.cache[i].age--; + } + } + slot->age = WRITE_CACHE_PAGES - 1; + + /* A page further on in this write may still sit, part written, in + * another buffer - left there by an earlier write that ended inside it. + * This write reaches that page through its own buffer, so the old one + * goes to flash first: otherwise two buffers hold the page, reads find + * the older, and its later flush programs stale data over the newer. + * The original firmware lacks this and loses data that way. */ + for (i = 0; i < WRITE_CACHE_PAGES; i++) + { + struct write_cache *c = &ftl.cache[i]; + uint32_t page = c->next_lba & ~page_mask; + + if (c != slot && c->valid && page >= page_lba && page < lba + count) + { + cache_flush(c); + } + } + + while (count) + { + uint32_t in_block = lba % sec_per_block(); + struct exch_slot *s = exch_for_write(lba & ~page_mask); + + slot->slot = s; + + if (!slot->valid) + { + /* start the page with what precedes this sector in it */ + uint16_t in_page = (uint16_t)(lba & page_mask); + uint8_t sppr = sec_per_page_raw(); + + if (in_page <= sppr) + { + flash_read(s->src + (in_block & ~page_mask), slot->buf, NULL, + (uint8_t)in_page); + } + else + { + uint32_t page_base = (s->src + in_block) & ~page_mask; + + flash_read(page_base, slot->buf, NULL, sppr); + flash_read(page_base + sppr, + slot->buf + (uint32_t)sppr * FLASH_SECTOR_SIZE, + NULL, (uint8_t)(in_page - sppr)); + } + slot->valid = true; + } + + memcpy(slot->buf + (lba & page_mask) * FLASH_SECTOR_SIZE, p, + FLASH_SECTOR_SIZE); + + if ((page_mask & ~lba) == 0) + { + /* the last sector of the page */ + slot->valid = false; + flash_program_page(s->cursor, slot->buf, NULL); + s->cursor += sec_per_page(); + if (s->cursor == s->end) + { + exch_close(s); + } + } + + p += FLASH_SECTOR_SIZE; + count--; + lba++; + slot->next_lba = lba; + } + + return 0; +} + +/* ---- interface ---- */ + +enum ftl_a_error ftl_a_mount(const struct ftl_a_config *config) +{ + enum ftl_a_error err; + + ftl.geo = flash_get_geometry(); + ftl.config = *config; + ftl.mounted = false; + + reset_state(); + + if (!probe_layout()) + { + err = FTL_A_ERR_LAYOUT; + } + else + { + /* the flash layer keeps writes out of the boot area, as the + * original firmware does - all writes, if the area is implausibly + * small */ + flash_set_boot_area(ftl.boot_blocks < 3 ? UINT32_MAX : + (uint32_t)ftl.boot_blocks * sec_per_block()); + + err = mount_scan(); + } + + if (err == FTL_A_OK && ftl.config.sys_sectors >= ftl.geo->total_sectors) + { + err = FTL_A_ERR_SYS_TOO_BIG; + } + + if (err == FTL_A_OK && ftl.format_flag != FORMAT_FLAG) + { + /* Another generation's flag, when the caller knows it. The older + * rk2705 NAND bootloader formats with 1; its write logic is the same + * as the flag-2 generation's, but writing such media stays the + * caller's decision. */ + if (ftl.config.alt_format_flag == 0 || + ftl.format_flag != ftl.config.alt_format_flag) + { + err = FTL_A_ERR_FORMAT_FLAG; + } + else if (!ftl.config.alt_format_writable) + { + set_write_protect(true); + } + } + + if (err == FTL_A_OK) + { + /* Recovery writes, and a wrong layout would make it relocate data + * into the wrong blocks: only on a confirmed one. Leaving an + * interrupted write unresolved is the safe side. */ + if (!ftl.write_protect) + { + recover_interrupted(); + } + ftl.mounted = true; + } + + ftl.error = err; + return err; +} + +void ftl_a_get_status(struct ftl_a_status *status) +{ + status->error = ftl.error; + status->confidence = ftl.confidence; + status->writable = !ftl.write_protect; + status->reserve_base = ftl.reserve_base; + status->sys_zone = system_zone(); + status->sys_offset = system_offset(); + status->base_fits = ftl.base_fits; + status->format_flag = ftl.format_flag; +} + +uint32_t ftl_a_capacity(int volume) +{ + uint32_t sectors = 0; + + if (ftl.mounted) + { + sectors = volume_sectors(volume); + } + return sectors; +} + +/* Requests are split in pieces the page-sized internals count in 16 bits. */ +#define MAX_REQUEST 0x8000 + +int ftl_a_read(int volume, uint32_t sector, void *buf, uint32_t count) +{ + uint8_t *p = buf; + int ret = 0; + + if (!ftl.mounted || (volume != FTL_A_VOL_SYS && volume != FTL_A_VOL_USER) || + sector + count > volume_sectors(volume)) + { + ret = 1; + } + + while (ret == 0 && count > 0) + { + uint16_t n = (count > MAX_REQUEST) ? MAX_REQUEST : (uint16_t)count; + + ret = read_sectors(volume, sector, p, n); + sector += n; + count -= n; + p += (size_t)n * FLASH_SECTOR_SIZE; + } + return ret; +} + +int ftl_a_write(int volume, uint32_t sector, const void *buf, uint32_t count) +{ + const uint8_t *p = buf; + int ret = 0; + + if (!ftl.mounted || ftl.write_protect || + (volume != FTL_A_VOL_SYS && volume != FTL_A_VOL_USER) || + sector + count > volume_sectors(volume)) + { + ret = 1; + } + + while (ret == 0 && count > 0) + { + uint16_t n = (count > MAX_REQUEST) ? MAX_REQUEST : (uint16_t)count; + + ret = write_sectors(volume, sector, p, n); + sector += n; + count -= n; + p += (size_t)n * FLASH_SECTOR_SIZE; + } + return ret; +} + +void ftl_a_sync(void) +{ + if (ftl.mounted && !ftl.write_protect) + { + cache_flush_all(); + } +} diff --git a/firmware/target/arm/rk27xx/ftl-scheme-a.h b/firmware/target/arm/rk27xx/ftl-scheme-a.h new file mode 100644 index 0000000000..1ec9e916c2 --- /dev/null +++ b/firmware/target/arm/rk27xx/ftl-scheme-a.h @@ -0,0 +1,99 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2026 by Marcin Bukat + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +/* The "Scheme A" flash translation layer of rk27xx devices - the on-flash + * format the original firmware of many rk2705/rk2706 players uses, read and + * written compatibly so that the original firmware keeps working on the same + * media. See ftl-scheme-a.c for how it works. */ + +#ifndef __FTL_SCHEME_A_H__ +#define __FTL_SCHEME_A_H__ + +#include +#include + +/* The two volumes. SYS holds the original firmware and its resources; USER + * is the music storage. */ +#define FTL_A_VOL_SYS 0 +#define FTL_A_VOL_USER 1 + +struct ftl_a_config +{ + /* Size of the SYS volume. The one layout value the FTL cannot read from + * its own tables: it is recorded in the boot area's ID block. */ + uint32_t sys_sectors; + /* Never write, not even the repairs a mount normally makes. */ + bool read_only; + /* A format flag accepted besides the standard 2 (0 = none), and whether + * media carrying it may be written. See ftl_a_mount(). */ + uint8_t alt_format_flag; + bool alt_format_writable; +}; + +/* Why a mount failed */ +enum ftl_a_error +{ + FTL_A_OK = 0, + FTL_A_ERR_LAYOUT, /* the zone layout could not be recognised */ + FTL_A_ERR_NO_LOG_BLOCK, /* a zone has no remap log: damaged */ + FTL_A_ERR_ZERO_CAPACITY, /* the block counts never loaded */ + FTL_A_ERR_COUNTS_ERASED, /* ... or were read from an erased page */ + FTL_A_ERR_SYS_TOO_BIG, /* SYS larger than the chip */ + FTL_A_ERR_FORMAT_FLAG, /* not formatted, or an unknown generation */ + FTL_A_ERR_NEEDS_REPAIR, /* a log needs rewriting; not while read-only */ +}; + +/* How sure the mount is of the layout it recognised */ +enum ftl_a_confidence +{ + FTL_A_CONF_NONE = 0, + FTL_A_CONF_CONFIRMED, /* derived and cross-checked: writable */ + FTL_A_CONF_ASSUMED, /* partly assumed: read-only */ +}; + +struct ftl_a_status +{ + enum ftl_a_error error; + enum ftl_a_confidence confidence; + bool writable; + uint8_t reserve_base; /* first reserve-pool entry of a zone table */ + uint8_t sys_zone; /* zone holding the boot area */ + uint8_t sys_offset; /* boot-area blocks at the start of it */ + uint8_t base_fits; /* reserve bases that fit the media; 1 = sure */ + uint8_t format_flag; /* as read from the media */ +}; + +/* Mount the media. Returns FTL_A_OK or the reason it failed. Even a + * successful mount may be read-only - see ftl_a_get_status(). */ +enum ftl_a_error ftl_a_mount(const struct ftl_a_config *config); +void ftl_a_get_status(struct ftl_a_status *status); + +/* Sectors on a volume; 0 when not mounted */ +uint32_t ftl_a_capacity(int volume); + +/* Return 0, or non-zero if the request was refused or a sector could not be + * read reliably. Sectors are 512 bytes. */ +int ftl_a_read(int volume, uint32_t sector, void *buf, uint32_t count); +int ftl_a_write(int volume, uint32_t sector, const void *buf, uint32_t count); + +/* Write out everything still held in RAM */ +void ftl_a_sync(void); + +#endif /* __FTL_SCHEME_A_H__ */ diff --git a/firmware/target/arm/rk27xx/ftl-scheme-b.c b/firmware/target/arm/rk27xx/ftl-scheme-b.c new file mode 100644 index 0000000000..1a817ade18 --- /dev/null +++ b/firmware/target/arm/rk27xx/ftl-scheme-b.c @@ -0,0 +1,1780 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2026 by Marcin Bukat + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +/* THE SCHEME B FLASH TRANSLATION LAYER + * + * Later rk27xx players - the HiFiMAN HM-601 among them - keep their NAND in + * a different format from Scheme A's (ftl-scheme-a.c): one where every + * block says what it holds, so the whole mapping is rebuilt by scanning at + * every mount and there is no table to keep consistent. This file reads and + * writes that format as the original firmware does, so a device keeps + * working with its original firmware after Rockbox has written to it. + * + * It is a reimplementation from reverse engineering. The format and the + * behaviour were worked out by analysing the machine code of the HM-601's + * NAND bootloader and of a compiled Rockchip FTL object from the rk2808 + * platform, which handles the same format, and checked against dumps of + * the media; no source code was used. Where the two binaries differ, the + * HM-601's behaviour is followed and the place is marked. "The original" + * below means that observed behaviour. Where it is odd, it is reproduced on + * purpose and marked as such: the order in which blocks are used and pages + * cached is part of what is checked against it. + * + * + * BLOCKS + * + * The unit of mapping is the SUPER-BLOCK - on a two-plane chip, a physical + * block from each plane, used as one (flash-rk27xx.h) - and the logical + * space is a run of logical blocks of that size. The blocks before + * config.first_block are the boot area. Then come the FTL's system blocks, + * the last of them named by the bad-block table, then the data area. + * + * + * METADATA + * + * The original firmware sees 16 spare bytes per sector and keeps one 16-bit + * FIELD in the first two of each; of a page, the field of sector k is field + * k. rk27 media holds the first three bytes of each sector's spare, so a + * field is metadata bytes 0-1, and byte 2 is used only where the original + * stores 32-bit values. Field 0 of every page is its TAG: + * + * 0xf200 data 1 version, 2 logical block, 3 source block, + * 4 the block itself + * 0xf100 cache journal 1 journal version, 2-3 first sector of the page + * (32 bits: field 2 and byte 2 of sector 2 hold the + * low 24, field 3 the high 16), 4-5 the 32-bit mask + * of sectors the page holds + * 0xf000 bad-block table + * 0xf300 power-loss record + * 0xf800 page remap not used on the devices seen; such blocks are + * freed + * 0xffff erased + * + * A data block carries its header on every page, so the last page written + * can be found by binary search. + * + * + * THE MOUNT + * + * The bad-block table (two mirror blocks at the start of the FTL's area) + * gives the bad blocks, the logical block count, the format version and the + * system blocks. Then page 0 of every block of the data area is read: a data + * block claims its logical block, and of two claimants the newer version + * wins. The (source, destination) pair of the winner is remembered: a write + * may have been under way. A destination block that is not full is an + * EXCHANGE BLOCK still being written; it is reopened, its unwritten tail + * still to come from the source. Blocks claimed by nothing are free. + * + * Versions are 16 bits and compared allowing for wrap-around, as the HM-601 + * does; the rk2808 object compares 24 bits including the tag's low byte, + * which agrees on every device seen. + * + * + * WRITING + * + * A write to a logical block goes to an erased block - an exchange block: + * the pages before the write are copied from the old block, the new pages + * written after them, and the exchange stays open for following pages + * until it is full or its slot is needed; closing it copies the rest of the + * old block. Up to config.exch_blocks are open at once, reused least + * recently used first. The old block is then free, erased when it is next + * used. + * + * A copy puts on every page it programs one header built from the source + * block's page 0 - version + 1, the source block, the destination block - + * as the HM-601 does (the rk2808 object keeps each page's own spare). + * + * + * THE WRITE CACHE + * + * Writes of less than a page, and of single pages, go to a cache of 16 + * pages in RAM, each with a mask of the sectors it holds. Every change to a + * cached page is also written to the CACHE JOURNAL - up to 16 blocks of + * 0xf100 pages - so nothing is held only in RAM: the mount replays the + * newest 16 distinct pages of the journal into the cache. A page leaves the + * cache through an exchange block, the gaps in it read from the flash first, + * and its journal entry is then cancelled by a copy with an empty mask. + * + * + * NOT IMPLEMENTED + * + * - The low-level format: media without a bad-block table does not mount. + * - The page remap (0xf800) and the paging of the remap table to flash for + * more than 32768 logical blocks; neither is used on the devices seen. + * - Bad-block replacement after a failed program or erase, and refreshing + * blocks the ECC reports decaying. + */ + +#include +#include +#include + +#include "flash-rk27xx.h" +#include "ftl-scheme-b.h" + +/* ---- layout ---- */ + +#define MAX_BLOCKS 8192 /* super-blocks on the chip */ +#define MAX_LOGICAL_BLOCKS 8192 +#define MAX_EXCH_BLOCKS 32 /* as many as the rk2808 object keeps */ +#define CACHE_PAGES 16 +#define JOURNAL_BLOCKS 16 +#define BBT_SCAN_BLOCKS 50 /* blocks the bad-block table may be in */ +#define BBT_MAX 0x400 +#define FREE_QUEUE_MIN 0x80 +#define FREE_QUEUE_MAX 0x400 +#define RCV_PAIRS 0x400 /* (source, destination) pairs kept */ +#define RECHECK_PAGES 6 /* last pages of an exchange read back */ +#define SEEN_MAX 2048 /* journal pages a mount looks at */ +#define MIN_SEC_PER_PAGE 8 /* a journal page needs fields 0-7 */ + +#define PAGE_BYTES (FLASH_MAX_SEC_PER_PAGE * FLASH_SECTOR_SIZE) +#define PAGE_META_BYTES (FLASH_MAX_SEC_PER_PAGE * FLASH_META_SIZE) + +/* The original's write path decides between the cache and an exchange + * block on fixed sector counts, whatever the page size */ +#define CACHE_FIRST_MAX 16 /* a write up to this long keeps its first + * page in the cache */ +#define CACHE_WRITE_MAX 15 /* ... and its whole pages too */ + +/* ---- metadata ---- */ + +#define TAG_TABLE 0xf000 +#define TAG_CACHE 0xf100 +#define TAG_DATA 0xf200 +#define TAG_POWER 0xf300 +#define TAG_PAGE_REMAP 0xf800 +#define TAG_KIND 0xff00 +#define FIELD_ERASED 0xffff + +/* data page */ +#define F_TAG 0 +#define F_VER 1 +#define F_LBLOCK 2 +#define F_SRC 3 +#define F_SELF 4 +/* cache journal page */ +#define F_LBA_LO 2 +#define F_LBA_HI 3 +#define F_MASK_LO 4 +#define F_MASK_HI 5 +#define JOURNAL_FIELDS 8 +/* bad-block table page */ +#define F_LOGICAL 2 +#define F_MIRROR_A 4 +#define F_MIRROR_B 5 +#define F_POWER_BLOCK 6 +#define F_LAST_SYS_BLOCK 7 +#define BBT_FIELDS 8 +#define FORMAT_VERSION_MAJOR 2 + +/* power-loss record: three little-endian words of the page's first sector */ +#define PWR_MODE_DONE 0x55530000 +#define PWR_MODE_COPY 0x55531111 + +#define DATA_HEADER_FIELDS 5 +#define HEADER_PAD 0xff /* what the unused metadata bytes get */ + +#define NO_SLOT 0xff +#define NO_JOURNAL 0xffffffff + +/* system blocks named by the bad-block table */ +#define SYS_MIRROR_A 0 +#define SYS_MIRROR_B 1 +#define SYS_POWER 2 +#define SYS_LAST 3 +#define SYS_BLOCKS 4 + +/* ---- state ---- */ + +/* A logical block being rewritten into an exchange block */ +struct exch_block +{ + uint32_t start; /* next sector to write in the exchange block */ + uint32_t end; /* the sector after the exchange block */ + uint32_t src; /* first sector of the block being replaced */ + uint16_t ver; /* version of the exchange block's header */ + uint8_t age; /* exch_count - 1 = used most recently */ + bool open; +}; + +/* A page of the write cache */ +struct cache_page +{ + uint32_t lba; /* first sector of the page */ + uint32_t mask; /* the sectors held */ + uint8_t age; /* 15 = used most recently */ + bool valid; +}; + +/* The blocks waiting to be used, oldest first. A block is only ever in it + * once, and one queued when it is full is dropped - so a mount that finds + * more free blocks than the queue holds leaves the rest unused until the + * next mount, as the original does. */ +struct free_queue +{ + uint16_t max; + uint16_t front; + uint16_t rear; + uint16_t count; + uint16_t blk[FREE_QUEUE_MAX]; +}; + +static struct +{ + const struct flash_geometry *geo; + struct ftl_b_config config; + bool mounted; + bool writable; + enum ftl_b_error error; + + uint16_t total_blocks; + uint16_t logical_blocks; + uint16_t format_version; + uint16_t sys_block[SYS_BLOCKS]; + uint16_t bbt_page; /* sector of the newest table page, in block */ + uint16_t bad_count; + uint16_t bad[BBT_MAX]; + + uint16_t remap[MAX_LOGICAL_BLOCKS]; /* 0 = not mapped */ + struct free_queue free; + struct exch_block exch[MAX_EXCH_BLOCKS]; + + /* the cache journal: its blocks, oldest first, the one being written + * and the next sector in it */ + uint16_t journal_blk[JOURNAL_BLOCKS]; + int8_t journal_slot; /* -1 = no journal yet */ + uint32_t journal_page; /* NO_JOURNAL = the block is full */ + uint16_t journal_ver; + + struct cache_page cache[CACHE_PAGES]; + /* the valid cache pages by ascending LBA, NO_SLOT-terminated */ + uint8_t order[CACHE_PAGES + 1]; +} ftl; + +static uint8_t cache_buf[CACHE_PAGES][PAGE_BYTES] __attribute__((aligned(4))); + +/* One page of scratch: the bad-block table as read, the journal pages a + * mount has seen. */ +static uint8_t page_buf[PAGE_BYTES] __attribute__((aligned(4))); + +/* ---- geometry helpers ---- */ + +static uint8_t sec_per_page(void) +{ + return ftl.geo->sec_per_page; +} + +static uint16_t sec_per_block(void) +{ + return ftl.geo->sec_per_block; +} + +static uint32_t block_sector(uint16_t blk) +{ + return (uint32_t)blk * sec_per_block(); +} + +static uint16_t sector_block(uint32_t sec) +{ + return (uint16_t)(sec / sec_per_block()); +} + +/* The sectors of a page from sector `sec`: every bit up to the page size */ +static uint32_t page_mask(uint32_t n) +{ + return n >= 32 ? 0xffffffff : ((uint32_t)1 << n) - 1; +} + +/* ---- metadata helpers ---- */ + +static uint16_t field(const uint8_t *meta, unsigned k) +{ + return (uint16_t)(meta[k * FLASH_META_SIZE] + | (meta[k * FLASH_META_SIZE + 1] << 8)); +} + +static void set_field(uint8_t *meta, unsigned k, uint16_t v) +{ + meta[k * FLASH_META_SIZE] = (uint8_t)v; + meta[k * FLASH_META_SIZE + 1] = (uint8_t)(v >> 8); +} + +/* The original stores a 32-bit word in the slot: the low three bytes reach + * the media. */ +static void set_word(uint8_t *meta, unsigned k, uint32_t v) +{ + meta[k * FLASH_META_SIZE] = (uint8_t)v; + meta[k * FLASH_META_SIZE + 1] = (uint8_t)(v >> 8); + meta[k * FLASH_META_SIZE + 2] = (uint8_t)(v >> 16); +} + +/* v is a later version than `than`. The HM-601's comparison: plain, or with + * both shifted half the range, so that a wrapped version still wins. */ +static bool ver_newer(uint16_t v, uint16_t than) +{ + return than < v || (uint16_t)(than + 0x8000) < (uint16_t)(v + 0x8000); +} + +/* ---- free blocks ---- */ + +static void queue_in(uint16_t blk) +{ + struct free_queue *q = &ftl.free; + bool queued = q->count == q->max; + uint16_t i; + + for (i = 0; i < q->count && !queued; i++) + { + queued = q->blk[(q->front + i) % q->max] == blk; + } + if (!queued) + { + q->blk[q->rear] = blk; + q->count++; + q->rear = (uint16_t)((q->rear + 1) % q->max); + } +} + +/* Returns 0 when there is none: block 0 is the boot area's. */ +static uint16_t queue_out(void) +{ + struct free_queue *q = &ftl.free; + uint16_t blk = 0; + + if (q->count > 0) + { + q->count--; + blk = q->blk[q->front]; + q->front = (uint16_t)((q->front + 1) % q->max); + } + return blk; +} + +static bool block_is_bad(uint16_t blk) +{ + bool bad = false; + uint16_t i; + + for (i = 0; i < ftl.bad_count && !bad; i++) + { + bad = ftl.bad[i] == blk; + } + return bad; +} + +/* ---- mapping ---- */ + +/* Map logical block `lb` to `blk`, unless blk is 0 or past the chip. Returns + * the previous mapping; 0 for a logical block out of range. */ +static uint16_t remap_set(uint16_t lb, uint16_t blk) +{ + uint16_t old = 0; + + if (lb < ftl.logical_blocks) + { + old = ftl.remap[lb]; + if (blk != 0 && blk < ftl.total_blocks) + { + ftl.remap[lb] = blk; + } + } + return old; +} + +/* The logical block stored in the block holding sector `sec`; + * logical_blocks if none. */ +static uint16_t remap_owner(uint32_t sec) +{ + uint16_t blk = sector_block(sec); + uint16_t lb; + + for (lb = 0; lb < ftl.logical_blocks && ftl.remap[lb] != blk; lb++) + { + } + return lb; +} + +/* The sector logical sector `lba` is mapped to. A logical block never + * written is given a free block here - even to be read, as the original + * does; its content is then whatever that block holds. With `current`, a + * sector of an exchange block not yet written is found in the source block + * instead. */ +static uint32_t remap_sector(uint32_t lba, bool current) +{ + uint16_t lb = sector_block(lba); + uint16_t blk = remap_set(lb, 0); + uint32_t sec; + uint8_t i; + + if (blk == 0) + { + blk = queue_out(); + remap_set(lb, blk); + } + sec = block_sector(blk) + lba % sec_per_block(); + + for (i = 0; current && i < ftl.config.exch_blocks; i++) + { + const struct exch_block *e = &ftl.exch[i]; + + if (e->open && e->start <= sec && sec < e->end) + { + sec = e->src + sec % sec_per_block(); + break; + } + } + return sec; +} + +/* ---- copying ---- */ + +/* Copy n sectors as the HM-601 does: if the source block is a data block, + * every page programmed gets the header of the source's page 0 with the + * version advanced and the source and destination blocks filled in; any + * other block is copied with each sector's own metadata. */ +static void copy_sectors(uint32_t src, uint32_t dst, uint32_t n) +{ + uint8_t meta[PAGE_META_BYTES]; + + if (n > 0) + { + flash_read(src - src % sec_per_block(), NULL, meta, sec_per_page()); + + if (field(meta, F_TAG) == TAG_DATA) + { + set_field(meta, F_VER, (uint16_t)(field(meta, F_VER) + 1)); + set_field(meta, F_SRC, sector_block(src)); + set_field(meta, F_SELF, sector_block(dst)); + flash_copy_meta(src, dst, n, meta); + } + else + { + flash_copy_meta(src, dst, n, NULL); + } + } +} + +/* ---- exchange blocks ---- */ + +static void exch_close(struct exch_block *e) +{ + if (e->open) + { + copy_sectors(e->src + e->start % sec_per_block(), e->start, + e->end - e->start); + queue_in(sector_block(e->src)); + e->open = false; + } +} + +/* Open an exchange block for the logical block mapped at `sec`, in the slot + * of an exchange already on that block, else a free slot, else the least + * recently used. */ +static struct exch_block *exch_open(uint32_t lba, uint32_t sec) +{ + struct exch_block *e = NULL; + uint8_t meta[FLASH_META_SIZE]; + uint32_t offset = sec % sec_per_block(); + uint16_t blk; + uint8_t i; + + for (i = 0; i < ftl.config.exch_blocks && e == NULL; i++) + { + if (ftl.exch[i].open + && sector_block(ftl.exch[i].start) == sector_block(sec)) + { + e = &ftl.exch[i]; + exch_close(e); + } + } + for (i = 0; i < ftl.config.exch_blocks && e == NULL; i++) + { + if (!ftl.exch[i].open) + { + e = &ftl.exch[i]; + } + } + for (i = 0; i < ftl.config.exch_blocks && e == NULL; i++) + { + if (ftl.exch[i].age == 0) + { + e = &ftl.exch[i]; + exch_close(e); + } + } + + /* the version follows the block being replaced: field 1 of its second + * sector (HM-601: 16 bits, where the rk2808 object lets it reach 2^16) */ + flash_read(sec - offset + 1, NULL, meta, 1); + e->ver = (uint16_t)(field(meta, 0) + 1); + + blk = queue_out(); + remap_set(sector_block(lba), blk); + e->start = block_sector(blk); + e->end = e->start + sec_per_block(); + e->open = true; + flash_erase(e->start); + + e->src = sec - offset; + copy_sectors(e->src, e->start, offset); + e->start += offset; + return e; +} + +/* The exchange block to write the page at `lba` to: the one writing that + * block, if the page is not behind its cursor - pages skipped are copied + * forward - or a new one. */ +static struct exch_block *exch_for_write(uint32_t lba) +{ + uint32_t sec = remap_sector(lba, false); + struct exch_block *e = NULL; + uint8_t i; + + for (i = 0; i < ftl.config.exch_blocks && e == NULL; i++) + { + struct exch_block *x = &ftl.exch[i]; + + if (x->open && x->start <= sec && sec < x->end) + { + if (x->start < sec) + { + copy_sectors(x->src + x->start % sec_per_block(), x->start, + sec - x->start); + x->start = sec; + } + e = x; + } + } + if (e == NULL) + { + e = exch_open(lba, sec); + } + + for (i = 0; i < ftl.config.exch_blocks; i++) + { + if (e->age < ftl.exch[i].age) + { + ftl.exch[i].age--; + } + } + e->age = (uint8_t)(ftl.config.exch_blocks - 1); + return e; +} + +/* The page at `lba` is the next one an exchange block writes */ +static bool exch_at(uint32_t lba) +{ + uint32_t sec = remap_sector(lba, false); + bool at = false; + uint8_t i; + + for (i = 0; i < ftl.config.exch_blocks && !at; i++) + { + at = ftl.exch[i].open && ftl.exch[i].start == sec + && sec < ftl.exch[i].end; + } + return at; +} + +/* Write one page, at a page-aligned `lba`, through an exchange block */ +static void write_page(uint32_t lba, const void *data) +{ + struct exch_block *e = exch_for_write(lba); + uint8_t meta[PAGE_META_BYTES]; + + memset(meta, HEADER_PAD, sizeof(meta)); + set_field(meta, F_TAG, TAG_DATA); /* HM-601: no version bits in it */ + set_field(meta, F_VER, e->ver); + set_field(meta, F_LBLOCK, sector_block(lba)); + set_field(meta, F_SRC, sector_block(e->src)); + set_field(meta, F_SELF, sector_block(e->start)); + + flash_program(e->start, data, meta, sec_per_page()); + e->start += sec_per_page(); + if (e->start == e->end) + { + exch_close(e); + } +} + +/* ---- the write cache ---- */ + +static uint8_t cache_index(const struct cache_page *c) +{ + return (uint8_t)(c - ftl.cache); +} + +static uint8_t *cache_data(const struct cache_page *c) +{ + return cache_buf[cache_index(c)]; +} + +/* The valid cache page holding `lba`; CACHE_PAGES if none */ +static uint8_t cache_match(uint32_t lba) +{ + uint32_t page = lba - lba % sec_per_page(); + uint8_t i; + + for (i = 0; + i < CACHE_PAGES && !(ftl.cache[i].valid && ftl.cache[i].lba == page); + i++) + { + } + return i; +} + +static uint8_t cache_first_free(void) +{ + uint8_t i; + + for (i = 0; i < CACHE_PAGES && ftl.cache[i].valid; i++) + { + } + return i; +} + +static void journal_new_block(void) +{ + uint16_t blk = queue_out(); + + ftl.journal_blk[ftl.journal_slot] = blk; + flash_erase(block_sector(blk)); +} + +static void journal_program(const struct cache_page *c) +{ + uint8_t meta[PAGE_META_BYTES]; + + memset(meta, 0xff, sizeof(meta)); + set_word(meta, F_TAG, TAG_CACHE); /* HM-601: no version bits in it */ + set_word(meta, F_VER, ftl.journal_ver); + set_word(meta, F_LBA_LO, c->lba); + set_word(meta, F_LBA_HI, c->lba >> 16); + set_word(meta, F_MASK_LO, c->mask); + set_word(meta, F_MASK_HI, c->mask >> 16); + set_word(meta, 6, 0); + set_word(meta, 7, 0); + + /* the whole page buffer, the sectors the mask leaves out included */ + flash_program(block_sector(ftl.journal_blk[ftl.journal_slot]) + + ftl.journal_page, cache_data(c), meta, sec_per_page()); + ftl.journal_page += sec_per_page(); +} + +/* Record cache page `c` in the journal. When the journal has used all its + * blocks it starts again from a new first block, freeing the others, and + * records every cached page there - `c` among them. */ +static void cache_write(struct cache_page *c) +{ + uint8_t i; + + if (c->valid) + { + if (ftl.journal_slot < 0) + { + ftl.journal_slot = 0; + ftl.journal_page = 0; + journal_new_block(); + journal_program(c); + } + else if (ftl.journal_page < sec_per_block()) + { + journal_program(c); + } + else + { + ftl.journal_ver++; + ftl.journal_page = 0; + ftl.journal_slot = + (int8_t)((ftl.journal_slot + 1) % JOURNAL_BLOCKS); + + if (ftl.journal_slot != 0) + { + journal_new_block(); + journal_program(c); + } + else + { + for (i = 0; i < JOURNAL_BLOCKS; i++) + { + queue_in(ftl.journal_blk[i]); + ftl.journal_blk[i] = 0; + } + journal_new_block(); + + /* recursion one level deep: a fresh block has room */ + for (i = 0; i < CACHE_PAGES && ftl.order[i] != NO_SLOT; i++) + { + cache_write(&ftl.cache[ftl.order[i]]); + } + } + } + } +} + +/* Drop cache page `c`, journalling it with an empty mask */ +static void cache_invalidate(struct cache_page *c) +{ + uint8_t idx = cache_index(c); + uint8_t i, k; + + for (i = 0; i < CACHE_PAGES; i++) + { + if (ftl.order[i] == idx) + { + c->mask = 0; + cache_write(c); + c->valid = false; + for (k = i; k < CACHE_PAGES; k++) + { + ftl.order[k] = ftl.order[k + 1]; + } + break; + } + } +} + +static int read_sectors(uint32_t lba, uint8_t *buf, uint32_t count); + +/* Write cache page `c` to its block - the sectors it does not hold read + * from the flash first - and drop it. */ +static void cache_flush(struct cache_page *c) +{ + uint8_t *data = cache_data(c); + uint8_t i; + + if (c->valid) + { + for (i = 0; i < sec_per_page(); i++) + { + if (!(c->mask & ((uint32_t)1 << i))) + { + read_sectors(c->lba + i, + data + (size_t)i * FLASH_SECTOR_SIZE, 1); + } + } + write_page(c->lba, data); + cache_invalidate(c); + } +} + +/* Make room in a full cache. A run of more than three consecutive pages - + * a sequential write - is flushed, from the first page an exchange block is + * waiting for, or all but its last page. Otherwise, or if that freed no + * more than two, the least recently used page goes, with the pages that + * follow it consecutively. Returns the slot to use. */ +static uint8_t cache_evict(void) +{ + uint8_t best = 0, best_start = 0, cur = 0, cur_start = 0; + uint8_t slot = CACHE_PAGES; + bool lru = true; + uint8_t j, k, n, p; + + for (j = 0; j < CACHE_PAGES && ftl.order[j + 1] != NO_SLOT; j++) + { + if (ftl.cache[ftl.order[j]].lba + sec_per_page() + == ftl.cache[ftl.order[j + 1]].lba) + { + if (cur == 0) + { + cur_start = j; + } + cur++; + } + else + { + if (best < cur) + { + best_start = cur_start; + best = cur; + } + cur = 0; + } + } + if (best < cur) + { + best_start = cur_start; + best = cur; + } + + /* best counts the steps of the run: it holds best + 1 pages. Each page + * flushed leaves the order, the next taking its position. */ + if (best + 1 > 3) + { + for (k = 0; + k < best && !exch_at(ftl.cache[ftl.order[best_start + k]].lba); + k++) + { + } + if (k < best) + { + for (n = (uint8_t)(best - k); n > 0; n--) + { + cache_flush(&ftl.cache[ftl.order[best_start + k]]); + } + lru = best + 1 - k <= 3; + } + else + { + for (n = best; n > 0; n--) + { + cache_flush(&ftl.cache[ftl.order[best_start]]); + } + lru = false; + } + } + + if (lru) + { + slot = 0; + for (j = 1; j < CACHE_PAGES; j++) + { + if (ftl.cache[j].age < ftl.cache[slot].age) + { + slot = j; + } + } + for (p = 0; p < CACHE_PAGES && ftl.order[p] != slot; p++) + { + } + while (p < CACHE_PAGES && ftl.order[p] != NO_SLOT) + { + uint8_t this = ftl.order[p], next = ftl.order[p + 1]; + bool chained = next != NO_SLOT && + ftl.cache[this].lba + sec_per_page() == ftl.cache[next].lba; + + cache_flush(&ftl.cache[this]); + if (!chained) + { + break; + } + } + } + else + { + slot = cache_first_free(); + } + return slot; +} + +/* The cache page for `lba`, opened if not cached */ +static struct cache_page *cache_open(uint32_t lba) +{ + uint8_t slot = cache_match(lba); + bool hit = slot < CACHE_PAGES; + struct cache_page *c; + uint8_t i, j; + + if (!hit) + { + slot = cache_first_free(); + if (slot == CACHE_PAGES) + { + slot = cache_evict(); + } + } + + c = &ftl.cache[slot]; + c->valid = true; + c->lba = lba - lba % sec_per_page(); + + if (!hit) + { + for (j = 0; j < CACHE_PAGES && ftl.order[j] != NO_SLOT && + !(c->lba < ftl.cache[ftl.order[j]].lba); j++) + { + } + if (j < CACHE_PAGES && ftl.order[j] != NO_SLOT) + { + for (i = CACHE_PAGES - 1; i >= j && i < CACHE_PAGES; i--) + { + ftl.order[i + 1] = ftl.order[i]; + } + } + ftl.order[j] = slot; + } + + for (i = 0; i < CACHE_PAGES; i++) + { + if (c->age < ftl.cache[i].age) + { + ftl.cache[i].age--; + } + } + c->age = CACHE_PAGES - 1; + return c; +} + +/* ---- reading ---- */ + +static int read_sectors(uint32_t lba, uint8_t *buf, uint32_t count) +{ + int ret = 0; + + while (count > 0) + { + uint32_t sec = remap_sector(lba, true); + uint32_t off = lba % sec_per_page(); + uint32_t len = sec_per_page() - off; + uint32_t page = lba - off; + uint8_t k; + + if (len > count) + { + len = count; + } + if (flash_read(sec, buf, NULL, len)) + { + ret = 1; + } + + /* the cache has the newest copy of what it holds */ + for (k = 0; k < CACHE_PAGES && ftl.order[k] != NO_SLOT; k++) + { + const struct cache_page *c = &ftl.cache[ftl.order[k]]; + + if (c->lba == page) + { + uint32_t i; + + for (i = 0; i < len && c->mask != 0; i++) + { + if (c->mask & ((uint32_t)1 << (off + i))) + { + memcpy(buf + i * FLASH_SECTOR_SIZE, + cache_data(c) + (off + i) * FLASH_SECTOR_SIZE, + FLASH_SECTOR_SIZE); + } + } + break; + } + } + + lba += len; + buf += len * FLASH_SECTOR_SIZE; + count -= len; + } + return ret; +} + +/* ---- writing ---- */ + +/* Put `n` sectors from `buf` into cache page `c`, from sector `off` */ +static void cache_put(struct cache_page *c, uint32_t off, const uint8_t *buf, + uint32_t n) +{ + memcpy(cache_data(c) + off * FLASH_SECTOR_SIZE, buf, n * FLASH_SECTOR_SIZE); + c->mask |= page_mask(n) << off; +} + +static void write_sectors(uint32_t lba, const uint8_t *buf, uint32_t count) +{ + uint32_t spp = sec_per_page(); + uint32_t off = lba % spp; + uint32_t left = count; + bool done = false; + uint8_t k; + + if (off != 0) + { + uint32_t n = spp - off; + struct cache_page *c; + + if (n > count) + { + n = count; + } + c = cache_open(lba); + cache_put(c, off, buf, n); + buf += n * FLASH_SECTOR_SIZE; + lba += n; + left -= n; + if (count <= CACHE_FIRST_MAX) + { + cache_write(c); + } + else + { + cache_flush(c); + } + } + + while (!done) + { + if (left < spp) + { + if (left > 0) + { + struct cache_page *c = cache_open(lba); + + cache_put(c, 0, buf, left); + cache_write(c); + } + done = true; + } + else + { + /* odd but original: on the length of the whole request */ + if (count <= CACHE_WRITE_MAX && !exch_at(lba)) + { + struct cache_page *c = cache_open(lba); + + cache_put(c, 0, buf, spp); + cache_write(c); + } + else + { + /* the page before this one goes first, so a sequential + * write continues the exchange block; odd but original: + * the first in the order is never looked at */ + for (k = CACHE_PAGES - 1; k > 0; k--) + { + uint8_t i = ftl.order[k]; + + if (i != NO_SLOT && ftl.cache[i].lba + spp == lba) + { + cache_flush(&ftl.cache[i]); + break; + } + } + k = cache_match(lba); + if (k < CACHE_PAGES) + { + cache_invalidate(&ftl.cache[k]); + } + write_page(lba, buf); + } + buf += spp * FLASH_SECTOR_SIZE; + lba += spp; + left -= spp; + } + } +} + +/* ---- mount ---- */ + +/* Read the bad-block table: the newest page of the two mirror blocks among + * the first BBT_SCAN_BLOCKS of the FTL's area. Mirrors left at different + * pages are rewritten from the newer, as the original does on a writable + * mount. */ +static enum ftl_b_error read_bbt(void) +{ + enum ftl_b_error err = FTL_B_OK; + uint32_t addr[2] = { UINT32_MAX, UINT32_MAX }; + uint8_t meta[PAGE_META_BYTES]; + uint32_t spb = sec_per_block(); + uint32_t pick, off0, off1; + int found = -1; + uint16_t blk, i; + + for (blk = ftl.config.first_block; + blk < ftl.config.first_block + BBT_SCAN_BLOCKS + && blk < ftl.total_blocks && found < 1; + blk++) + { + uint32_t off; + + for (off = 0; off < spb; off += sec_per_page()) + { + if (flash_read(block_sector(blk) + off, NULL, meta, 1) == 0) + { + if (field(meta, F_TAG) != TAG_TABLE) + { + break; + } + if (off == 0) + { + found++; + } + if (found >= 0) + { + addr[found] = block_sector(blk) + off; + } + } + } + } + + if (found < 0) + { + err = FTL_B_ERR_NO_TABLE; + } + else + { + off0 = addr[0] % spb; + off1 = addr[1] % spb; + pick = addr[0]; + if (off0 != off1 && addr[1] != UINT32_MAX + && (addr[0] == UINT32_MAX || off1 >= off0)) + { + pick = addr[1]; + } + + flash_read(pick, page_buf, meta, sec_per_page()); + + if (off0 != off1) + { + uint16_t a = field(meta, F_MIRROR_A), b = field(meta, F_MIRROR_B); + + if (sector_block(pick) == a) + { + addr[0] = block_sector(b); + addr[1] = block_sector(a); + } + else + { + addr[1] = block_sector(b); + addr[0] = block_sector(a); + } + + if (a < ftl.config.first_block || b < ftl.config.first_block || + a >= ftl.total_blocks || b >= ftl.total_blocks) + { + err = FTL_B_ERR_TABLE; + } + else if (ftl.writable) + { + flash_erase(addr[0]); + flash_program(addr[0], page_buf, meta, sec_per_page()); + flash_erase(addr[1]); + flash_program(addr[1], page_buf, meta, sec_per_page()); + } + } + } + + if (err == FTL_B_OK) + { + const uint8_t *tbl = page_buf; + + ftl.format_version = field(meta, F_VER); + ftl.sys_block[SYS_POWER] = field(meta, F_POWER_BLOCK); + ftl.sys_block[SYS_LAST] = field(meta, F_LAST_SYS_BLOCK); + ftl.logical_blocks = field(meta, F_LOGICAL); + + for (i = 0; + i < BBT_MAX && (tbl[2 * i] | (tbl[2 * i + 1] << 8)) != 0xffff; + i++) + { + ftl.bad[i] = (uint16_t)(tbl[2 * i] | (tbl[2 * i + 1] << 8)); + } + ftl.bad_count = i; + + ftl.sys_block[SYS_MIRROR_A] = + sector_block(addr[1] < addr[0] ? addr[1] : addr[0]); + ftl.sys_block[SYS_MIRROR_B] = + sector_block(addr[1] < addr[0] ? addr[0] : addr[1]); + ftl.bbt_page = (uint16_t)(addr[0] % spb); + + if ((ftl.format_version >> 8) != FORMAT_VERSION_MAJOR) + { + err = FTL_B_ERR_VERSION; + } + else if (ftl.logical_blocks > MAX_LOGICAL_BLOCKS || + ftl.sys_block[SYS_LAST] >= ftl.total_blocks) + { + err = FTL_B_ERR_TOO_BIG; + } + } + return err; +} + +/* Finish a refresh of a system block that power loss interrupted: its + * record says the block's content waits in a temporary block. Unlike the + * original, which also replays its "done" record - a copy of block 0 onto + * itself - only an unfinished copy is redone. */ +static void power_loss_recover(void) +{ + uint32_t base = block_sector(ftl.sys_block[SYS_POWER]); + uint8_t meta[FLASH_META_SIZE]; + uint32_t mode = 0, src = 0, dst = 0; + bool any = false; + uint32_t off; + + for (off = 0; off < sec_per_block(); off += sec_per_page()) + { + if (flash_read(base + off, page_buf, meta, 1) == 0) + { + if (field(meta, 0) != TAG_POWER) + { + break; + } + mode = page_buf[0] | page_buf[1] << 8 | page_buf[2] << 16 + | (uint32_t)page_buf[3] << 24; + src = page_buf[4] | page_buf[5] << 8 | page_buf[6] << 16 + | (uint32_t)page_buf[7] << 24; + dst = page_buf[8] | page_buf[9] << 8 | page_buf[10] << 16 + | (uint32_t)page_buf[11] << 24; + any = true; + } + } + + if (any && ftl.writable) + { + if (mode == PWR_MODE_COPY && src < ftl.geo->total_sectors + && dst < ftl.geo->total_sectors) + { + flash_erase(dst); + copy_sectors(src, dst, sec_per_block()); + } + flash_erase(base); + } +} + +/* The mount's working set lives in the cache buffers, free until the + * journal is replayed into them */ +#define SCAN_USED ((uint32_t *)cache_buf[0]) +#define SCAN_PAIRS ((uint16_t *)cache_buf[1]) + +static void mark_used(uint16_t blk, bool used) +{ + if (blk < ftl.total_blocks) + { + if (used) + { + SCAN_USED[blk / 32] |= (uint32_t)1 << (blk % 32); + } + else + { + SCAN_USED[blk / 32] &= ~((uint32_t)1 << (blk % 32)); + } + } +} + +static bool is_used(uint16_t blk) +{ + return (SCAN_USED[blk / 32] >> (blk % 32)) & 1; +} + +/* Remember the (source, destination) of the logical block now stored in + * `blk`, replacing the pair of the block it displaced, `old` */ +static void add_pair(uint16_t *npairs, uint16_t old, uint16_t src, uint16_t dst) +{ + uint16_t k; + + for (k = 0; k < RCV_PAIRS && SCAN_PAIRS[2 * k + 1] != old; k++) + { + } + if (k == RCV_PAIRS && *npairs < RCV_PAIRS) + { + k = (*npairs)++; + } + if (k < RCV_PAIRS) + { + SCAN_PAIRS[2 * k] = src; + SCAN_PAIRS[2 * k + 1] = dst; + } +} + +/* A data block claiming logical block `lb`: the newer of it and the block + * claiming it already wins */ +static void scan_data_block(uint16_t blk, const uint8_t *meta, uint16_t *npairs) +{ + uint16_t lb = field(meta, F_LBLOCK); + uint16_t old = ftl.remap[lb]; + + if (old == 0) + { + mark_used(blk, true); + ftl.remap[lb] = blk; + } + else + { + uint8_t old_meta[DATA_HEADER_FIELDS * FLASH_META_SIZE]; + uint16_t src, dst; + + flash_read(block_sector(old), NULL, old_meta, DATA_HEADER_FIELDS); + + if (ver_newer(field(meta, F_VER), field(old_meta, F_VER))) + { + mark_used(old, false); + mark_used(blk, true); + ftl.remap[lb] = blk; + src = field(meta, F_SRC); + dst = field(meta, F_SELF); + } + else + { + src = field(old_meta, F_SRC); + dst = field(old_meta, F_SELF); + } + add_pair(npairs, old, src, dst); + } +} + +/* A journal block: keep the JOURNAL_BLOCKS newest, oldest first, in + * journal_blk[] with their versions in vers[]. vers[0] == 0 is taken for a + * free first entry. */ +static void scan_journal_block(uint16_t blk, uint16_t ver, uint16_t *vers, + uint8_t *count) +{ + int i = JOURNAL_BLOCKS - 1; + int k; + + while (i > JOURNAL_BLOCKS - 1 - *count && !ver_newer(ver, vers[i])) + { + i--; + } + + if (i > JOURNAL_BLOCKS - 1 - *count) + { + if (vers[0] == 0 && *count != JOURNAL_BLOCKS) + { + (*count)++; + } + else + { + mark_used(ftl.journal_blk[0], false); + queue_in(ftl.journal_blk[0]); + } + for (k = 0; k < i; k++) + { + vers[k] = vers[k + 1]; + ftl.journal_blk[k] = ftl.journal_blk[k + 1]; + } + vers[i] = ver; + ftl.journal_blk[i] = blk; + mark_used(blk, true); + } + else if (i < 0) + { + queue_in(blk); + } + else + { + vers[i] = ver; + ftl.journal_blk[i] = blk; + mark_used(blk, true); + (*count)++; + } +} + +/* An exchange the mount found: `dst` was being written with the content of + * `src`. If it is not full, reopen it - or, if one of its last pages cannot + * be read, finish it into a new block now, taking unreadable pages from the + * source. When it is full, the exchange finished and its source is free. */ +static void recover_exchange(uint16_t src_blk, uint16_t dst_blk, uint8_t *nexch) +{ + uint32_t spp = sec_per_page(); + uint32_t dst = block_sector(dst_blk), src = block_sector(src_blk); + int last = sec_per_block() / spp - 1; + int lo = 0, hi = last; + uint8_t meta[2 * FLASH_META_SIZE]; + uint16_t ver = 0; + uint32_t written, off; + uint16_t lb; + + flash_read(dst + spp * last, NULL, meta, 1); + if (field(meta, F_TAG) == FIELD_ERASED) + { + while (lo <= hi) + { + int mid = (lo + hi) / 2; + + flash_read(dst + spp * mid, NULL, meta, 2); + if (field(meta, F_TAG) == FIELD_ERASED) + { + hi = mid - 1; + } + else + { + ver = field(meta, F_VER); + lo = mid + 1; + } + } + } + written = spp * (hi + 1); + + if (written < sec_per_block()) + { + off = written > RECHECK_PAGES * spp ? written - RECHECK_PAGES * spp : 0; + while (off < written && flash_read(dst + off, NULL, NULL, spp) == 0) + { + off += spp; + } + + if (off < written && ftl.writable) + { + uint16_t nb = queue_out(); + uint32_t to; + + if (nb == 0) + { + nb = ftl.sys_block[SYS_POWER]; + } + to = block_sector(nb); + remap_set(remap_owner(dst), nb); + flash_erase(to); + copy_sectors(dst, to, off); + for (; off < written; off += spp) + { + if (flash_read(dst + off, NULL, NULL, spp) == 0) + { + copy_sectors(dst + off, to + off, spp); + } + else + { + copy_sectors(src + off, to + off, spp); + } + } + copy_sectors(src + written, to + written, + sec_per_block() - written); + queue_in(dst_blk); + queue_in(src_blk); + + /* odd but original: the block stays mapped to the power-loss + * block, which is then erased */ + if (nb == ftl.sys_block[SYS_POWER]) + { + copy_sectors(to, block_sector(queue_out()), sec_per_block()); + flash_erase(to); + } + } + else if (*nexch < ftl.config.exch_blocks) + { + struct exch_block *e = &ftl.exch[*nexch]; + + e->src = src; + e->start = dst + written; + e->end = dst + sec_per_block(); + e->ver = ver; + e->age = *nexch; + e->open = true; + (*nexch)++; + } + } + else if (src_blk < ftl.total_blocks) + { + /* finished: its source is free unless something maps it. The + * original would queue a source past the chip too. */ + for (lb = 0; lb < ftl.logical_blocks && ftl.remap[lb] != src_blk; lb++) + { + } + if (lb == ftl.logical_blocks) + { + queue_in(src_blk); + } + } +} + +/* Replay the journal into the cache: newest block first, newest page + * first, each page once, until the cache is full. A page with an empty + * mask cancels the older entries of its page. */ +static void replay_journal(void) +{ + uint32_t *seen = (uint32_t *)page_buf; + uint8_t meta[4 * FLASH_META_SIZE]; + uint8_t restored = 0; + int i; + + ftl.journal_page = NO_JOURNAL; + ftl.journal_slot = -1; + memset(page_buf, 0xff, sizeof(page_buf)); + + for (i = JOURNAL_BLOCKS - 1; i >= 0; i--) + { + int32_t off; + + if (ftl.journal_blk[i] == 0) + { + continue; + } + if (ftl.journal_slot < 0) + { + ftl.journal_slot = (int8_t)i; + } + + for (off = sec_per_block() - sec_per_page(); + off >= 0 && restored < CACHE_PAGES; + off -= sec_per_page()) + { + uint32_t sec = block_sector(ftl.journal_blk[i]) + off; + uint32_t lba, mask; + uint16_t k; + + if (flash_read(sec + F_LBA_LO, NULL, meta, 4) != 0) + { + continue; + } + /* byte 2 of the LBA word is never 0xff in a written page */ + if (meta[2] == 0xff) + { + ftl.journal_page = (uint32_t)off; + continue; + } + + lba = field(meta, 0) | (uint32_t)field(meta, 1) << 16; + mask = field(meta, 2) | (uint32_t)field(meta, 3) << 16; + + for (k = 0; + k < SEEN_MAX && seen[k] != 0xffffffff && seen[k] != lba; + k++) + { + } + if (k < SEEN_MAX && seen[k] == 0xffffffff) + { + if (mask != 0) + { + struct cache_page *c = cache_open(lba); + + restored++; + c->mask = mask; + flash_read(sec, cache_data(c), NULL, sec_per_page()); + } + seen[k] = lba; + } + } + } +} + +static enum ftl_b_error mount_scan(void) +{ + enum ftl_b_error err = FTL_B_OK; + uint16_t vers[JOURNAL_BLOCKS]; + uint8_t meta[DATA_HEADER_FIELDS * FLASH_META_SIZE]; + uint16_t npairs = 0; + uint8_t njournal = 0, nexch = 0; + uint16_t blk, k; + uint8_t i; + + ftl.total_blocks = (uint16_t)(ftl.geo->total_sectors / sec_per_block()); + ftl.free.max = ftl.total_blocks >> 5; + if (ftl.free.max < FREE_QUEUE_MIN) + { + ftl.free.max = FREE_QUEUE_MIN; + } + if (ftl.free.max > FREE_QUEUE_MAX) + { + ftl.free.max = FREE_QUEUE_MAX; + } + ftl.free.front = ftl.free.rear = ftl.free.count = 0; + + for (i = 0; i < MAX_EXCH_BLOCKS; i++) + { + ftl.exch[i].open = false; + ftl.exch[i].age = i; + } + for (i = 0; i < CACHE_PAGES; i++) + { + ftl.cache[i].valid = false; + ftl.cache[i].age = i; + ftl.cache[i].mask = 0; + ftl.cache[i].lba = 0; + ftl.order[i] = NO_SLOT; + } + ftl.order[CACHE_PAGES] = NO_SLOT; + + err = read_bbt(); + + if (err == FTL_B_OK) + { + memset(SCAN_USED, 0, (ftl.total_blocks + 31) / 32 * 4); + memset(SCAN_PAIRS, 0, RCV_PAIRS * 2 * sizeof(uint16_t)); + memset(ftl.remap, 0, sizeof(ftl.remap)); + memset(ftl.journal_blk, 0, sizeof(ftl.journal_blk)); + memset(vers, 0, sizeof(vers)); + + power_loss_recover(); + + for (blk = ftl.sys_block[SYS_LAST] + 1; blk < ftl.total_blocks; blk++) + { + uint16_t tag; + + if (block_is_bad(blk)) + { + mark_used(blk, true); + continue; + } + if (flash_read(block_sector(blk), NULL, meta, + DATA_HEADER_FIELDS) != 0) + { + continue; + } + + tag = field(meta, F_TAG); + if ((tag & TAG_KIND) == TAG_CACHE) + { + scan_journal_block(blk, field(meta, F_VER), vers, &njournal); + } + else if (tag == TAG_PAGE_REMAP) + { + queue_in(blk); + } + else if ((tag & TAG_KIND) == TAG_DATA + && field(meta, F_LBLOCK) < ftl.logical_blocks) + { + scan_data_block(blk, meta, &npairs); + } + } + + ftl.journal_ver = vers[JOURNAL_BLOCKS - 1]; + if (ftl.journal_blk[0] == 0) + { + /* close the gaps: oldest first from entry 0 */ + for (i = 0; i < JOURNAL_BLOCKS && ftl.journal_blk[i] == 0; i++) + { + } + for (k = 0; i < JOURNAL_BLOCKS; k++, i++) + { + ftl.journal_blk[k] = ftl.journal_blk[i]; + ftl.journal_blk[i] = 0; + } + } + + /* both blocks of an exchange stay out of the free queue */ + for (k = 0; k < npairs; k++) + { + mark_used(SCAN_PAIRS[2 * k], true); + mark_used(SCAN_PAIRS[2 * k + 1], true); + } + for (blk = ftl.sys_block[SYS_LAST] + 1; blk < ftl.total_blocks; blk++) + { + if (!is_used(blk)) + { + queue_in(blk); + } + } + + for (k = 0; k < RCV_PAIRS; k++) + { + uint16_t src = SCAN_PAIRS[2 * k], dst = SCAN_PAIRS[2 * k + 1]; + + /* the original reads past the chip for a destination that names + * no block; such a pair is skipped. A source past the chip - + * 0xffff in the blocks of a firmware image - only matters if the + * destination is not full. */ + if (dst != 0 && dst < ftl.total_blocks) + { + recover_exchange(src, dst, &nexch); + } + } + + replay_journal(); + } + return err; +} + +/* ---- interface ---- */ + +enum ftl_b_error ftl_b_mount(const struct ftl_b_config *config) +{ + enum ftl_b_error err = FTL_B_OK; + + ftl.mounted = false; + ftl.config = *config; + ftl.geo = flash_get_geometry(); + ftl.writable = !config->read_only; + + if (ftl.geo->sec_per_page < MIN_SEC_PER_PAGE || + ftl.geo->sec_per_page > FLASH_MAX_SEC_PER_PAGE || + ftl.geo->sec_per_block == 0 || + ftl.geo->total_sectors / ftl.geo->sec_per_block > MAX_BLOCKS) + { + err = FTL_B_ERR_GEOMETRY; + } + else if (config->exch_blocks == 0 + || config->exch_blocks > MAX_EXCH_BLOCKS + || config->first_block == 0) + { + err = FTL_B_ERR_CONFIG; + } + else + { + flash_set_meta_passthrough(true); + flash_set_boot_area(block_sector(config->first_block)); + flash_set_writable(ftl.writable); + + err = mount_scan(); + ftl.mounted = err == FTL_B_OK; + } + + if (!ftl.mounted) + { + ftl.writable = false; + flash_set_writable(false); + } + ftl.error = err; + return err; +} + +void ftl_b_get_status(struct ftl_b_status *status) +{ + uint8_t i; + + memset(status, 0, sizeof(*status)); + status->error = ftl.error; + status->writable = ftl.writable; + status->format_version = ftl.format_version; + status->logical_blocks = ftl.logical_blocks; + status->bad_blocks = ftl.bad_count; + status->free_blocks = ftl.free.count; + for (i = 0; ftl.mounted && i < ftl.config.exch_blocks; i++) + { + status->open_exch += ftl.exch[i].open; + } + for (i = 0; ftl.mounted && i < CACHE_PAGES; i++) + { + status->cached_pages += ftl.cache[i].valid; + } +} + +uint32_t ftl_b_capacity(void) +{ + uint32_t sectors = 0; + + if (ftl.mounted) + { + sectors = (uint32_t)ftl.logical_blocks * sec_per_block(); + } + return sectors; +} + +static bool range_valid(uint32_t sector, uint32_t count) +{ + uint32_t cap = ftl_b_capacity(); + + return sector < cap && count <= cap - sector; +} + +int ftl_b_read(uint32_t sector, void *buf, uint32_t count) +{ + int ret = 1; + + if (range_valid(sector, count)) + { + ret = read_sectors(sector, buf, count); + } + return ret; +} + +int ftl_b_write(uint32_t sector, const void *buf, uint32_t count) +{ + int ret = 1; + + if (ftl.writable && range_valid(sector, count)) + { + write_sectors(sector, buf, count); + ret = 0; + } + return ret; +} + +void ftl_b_sync(void) +{ +} diff --git a/firmware/target/arm/rk27xx/ftl-scheme-b.h b/firmware/target/arm/rk27xx/ftl-scheme-b.h new file mode 100644 index 0000000000..0d2a059ecb --- /dev/null +++ b/firmware/target/arm/rk27xx/ftl-scheme-b.h @@ -0,0 +1,87 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2026 by Marcin Bukat + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +/* The "Scheme B" flash translation layer of rk27xx devices - the + * self-describing on-flash format the original firmware of the HiFiMAN + * HM-601 and similar players uses, read and written compatibly so that the + * original firmware keeps working on the same media. A reimplementation + * from reverse engineering; see ftl-scheme-b.c for how it works. */ + +#ifndef __FTL_SCHEME_B_H__ +#define __FTL_SCHEME_B_H__ + +#include +#include + +struct ftl_b_config +{ + /* The first block the FTL owns; the boot area is everything before it. + * Not recorded in the FTL's own structures. */ + uint16_t first_block; + /* Exchange blocks open at once. The original firmware's own count must + * not be exceeded: its mount recovers no more than that many, and loses + * the data of the rest. 8 on the HM-601. */ + uint8_t exch_blocks; + /* Never write, not even the repairs a mount normally makes. */ + bool read_only; +}; + +/* Why a mount failed */ +enum ftl_b_error +{ + FTL_B_OK = 0, + FTL_B_ERR_GEOMETRY, /* the chip is not one this FTL can map */ + FTL_B_ERR_CONFIG, /* an unusable configuration */ + FTL_B_ERR_NO_TABLE, /* no bad-block table: not formatted */ + FTL_B_ERR_TABLE, /* the bad-block table is damaged */ + FTL_B_ERR_VERSION, /* a format generation not supported */ + FTL_B_ERR_TOO_BIG, /* more logical blocks than fit in RAM */ +}; + +struct ftl_b_status +{ + enum ftl_b_error error; + bool writable; + uint16_t format_version; /* as recorded in the bad-block table */ + uint16_t logical_blocks; + uint16_t bad_blocks; + uint16_t free_blocks; /* erased or erasable, queued for use */ + uint8_t open_exch; /* exchange blocks in progress */ + uint8_t cached_pages; /* pages held in the write cache */ +}; + +/* Mount the media. Returns FTL_B_OK or the reason it failed. */ +enum ftl_b_error ftl_b_mount(const struct ftl_b_config *config); +void ftl_b_get_status(struct ftl_b_status *status); + +/* Sectors in the logical space; 0 when not mounted */ +uint32_t ftl_b_capacity(void); + +/* Return 0, or non-zero if the request was refused or a sector could not be + * read reliably. Sectors are 512 bytes. */ +int ftl_b_read(uint32_t sector, void *buf, uint32_t count); +int ftl_b_write(uint32_t sector, const void *buf, uint32_t count); + +/* Every write is on the flash when ftl_b_write() returns - the write cache + * is journalled as it changes - so there is nothing to write out. Kept for + * the storage layer's interface. */ +void ftl_b_sync(void); + +#endif /* __FTL_SCHEME_B_H__ */ diff --git a/firmware/target/arm/rk27xx/ftl-target.h b/firmware/target/arm/rk27xx/ftl-target.h index e432ad1b40..fde3990a3d 100644 --- a/firmware/target/arm/rk27xx/ftl-target.h +++ b/firmware/target/arm/rk27xx/ftl-target.h @@ -23,22 +23,41 @@ #include "config.h" #include "inttypes.h" +#include -#ifdef BOOTLOADER -/* Bootloaders don't need write access */ -#define FTL_READONLY +/* The drives the NAND presents: USER, and SYS only when the target exposes + * it - see HAVE_RK27XX_NAND_SYS in the target config. */ +#ifdef HAVE_RK27XX_NAND_SYS +#define FTL_DRIVE_SYS 0 +#define FTL_DRIVE_USER 1 +#define FTL_NUM_DRIVES 2 +#else +#define FTL_DRIVE_USER 0 +#define FTL_NUM_DRIVES 1 #endif -/* Pointer to an info structure regarding the flash type used */ -extern const struct nand_device_info_type* ftl_nand_type; - -/* Number of banks we detected a chip on */ -extern uint32_t ftl_banks; +#if !defined(HAVE_RK27XX_NAND_SYS) && !defined(BOOTLOADER) \ + && defined(USB_ENABLE_STORAGE) +/* Without SYS as a drive of its own, the debug menu can ask for it to be + * shown to a USB host in place of USER, for one connection: the next one, + * or the current one if none has started. Kept in RAM only. */ +#define FTL_SYS_ON_USB +void ftl_set_sys_on_usb(bool on); +bool ftl_get_sys_on_usb(void); +#endif uint32_t ftl_init(void); -uint32_t ftl_read(uint32_t sector, uint32_t count, void* buffer); -uint32_t ftl_write(uint32_t sector, uint32_t count, const void* buffer); +uint32_t ftl_read(int drive, uint32_t sector, uint32_t count, void* buffer); +uint32_t ftl_write(int drive, uint32_t sector, uint32_t count, + const void* buffer); uint32_t ftl_sync(void); +/* Usable sectors on a logical disk, 0 if not mounted. Comes from the FTL's + * own tables, not from raw block geometry - see ftl-rk27xx.c. */ +uint32_t ftl_get_sectors(int drive); + +/* Whether a drive shows the SYS volume right now */ +bool ftl_drive_is_sys(int drive); + #endif diff --git a/firmware/target/arm/rk27xx/lcdif-rk27xx.c b/firmware/target/arm/rk27xx/lcdif-rk27xx.c index 618b476480..8a1927242b 100644 --- a/firmware/target/arm/rk27xx/lcdif-rk27xx.c +++ b/firmware/target/arm/rk27xx/lcdif-rk27xx.c @@ -95,6 +95,19 @@ static void lcdctrl_buff_setup(int width, int height) LINE3_UVADDR = LINE3_YADDR + 1; } +/* CSn/WEn/RDn timings in MCU mode, in AHB clocks: csrw (bits 9-7), rwpw + * (6-3), rwcs (2-0). csrw 1, rwpw 4, rwcs 1 makes a 120 ns write cycle at + * the 50 MHz bus clock, but 60 ns at the boosted 100 MHz - too fast for + * the panel: stray pixels, and partial updates landing in the wrong place. + * Twice the clocks keep it 120 ns there. */ +#define LCD_TIMING_50MHZ ((1<<7) | (4<<3) | 1) +#define LCD_TIMING_100MHZ ((2<<7) | (8<<3) | 2) + +void lcdif_set_bus_timing(bool boosted) +{ + VERT_PERIOD = boosted ? LCD_TIMING_100MHZ : LCD_TIMING_50MHZ; +} + static void lcdctrl_init(void) { int i; @@ -107,7 +120,9 @@ static void lcdctrl_init(void) LCDC_CTRL = ALPHA(7) | LCDC_STOP | LCDC_MCU | RGB24B; MCU_CTRL = ALPHA_BASE(0x3f) | MCU_CTRL_BYPASS; - VERT_PERIOD = (1<<7)|(1<<5)|1; /* CSn/WEn/RDn signal timings */ + /* for the clock running now: the firmware boosts before lcd_init(), + * and the bootloader runs at crt0's, which is CPUFREQ_MAX */ + lcdif_set_bus_timing(cpu_frequency == CPUFREQ_MAX); lcd_display_init(); lcdctrl_bypass(0); @@ -258,6 +273,27 @@ void lcd_update_rect(int x, int y, int width, int height) { int x_end, y_end, x_align, y_align; + /* Clip to the screen, as other targets do. A rect reaching past it + * set a GRAM window off the panel - nothing showed - and built more + * DMA descriptors than scr_llp[] holds, overwriting what follows it + * in memory: the kernel's queue list among it. */ + if (x < 0) + { + width += x; + x = 0; + } + if (y < 0) + { + height += y; + y = 0; + } + if (width > LCD_WIDTH - x) + width = LCD_WIDTH - x; + if (height > LCD_HEIGHT - y) + height = LCD_HEIGHT - y; + if (width <= 0 || height <= 0) + return; + /* min alowed transfer seems to be 4x4 pixels */ x_align = x & 3; y_align = y & 3; @@ -284,8 +320,11 @@ void lcd_update_rect(int x, int y, int width, int height) /* Setup buffered writes to lcd controler */ MCU_CTRL = MCU_CTRL_RS_HIGH|MCU_CTRL_BUFF_WRITE|MCU_CTRL_BUFF_START; - /* Wait for DMA transfer to finish */ - while (DWDMA_CTL_L(0) & (1<<27)); + /* Wait for the DMA transfer to finish: the channel disables itself + * after its last block. CTL_L's LLP_DST_EN (bit 27) is no measure - + * it clears as soon as the last block is loaded, while that block + * still runs. */ + while (DWDMA_DMA_CHEN & (1<<0)); } void lcd_update() diff --git a/firmware/target/arm/rk27xx/lcdif-rk27xx.h b/firmware/target/arm/rk27xx/lcdif-rk27xx.h index 4961863504..8461c7d6a9 100644 --- a/firmware/target/arm/rk27xx/lcdif-rk27xx.h +++ b/firmware/target/arm/rk27xx/lcdif-rk27xx.h @@ -1,6 +1,8 @@ #ifndef _LCDIF_RK27XX_H #define _LCDIF_RK27XX_H +#include + #define LCDIF_16BIT 16 #define LCDIF_18BIT 18 @@ -12,4 +14,7 @@ void lcd_display_init(void); void lcd_set_gram_area(int x_start, int y_start, int x_end, int y_end); +/* LCD bus timing for the bus clock: set before it rises, after it falls */ +void lcdif_set_bus_timing(bool boosted); + #endif /* _LCDIF_RK27XX_H */ diff --git a/firmware/target/arm/rk27xx/nand-rk27xx.c b/firmware/target/arm/rk27xx/nand-rk27xx.c index 2cf605e72b..a491d661bd 100644 --- a/firmware/target/arm/rk27xx/nand-rk27xx.c +++ b/firmware/target/arm/rk27xx/nand-rk27xx.c @@ -19,46 +19,28 @@ ****************************************************************************/ #include "config.h" + #include "system.h" + #include "string.h" #include "nand-target.h" + #include "rk27xx.h" -#if 0 -/* This is for documentation purpose as FTL has not been reverse engineered yet - * Raw nand handling functions based on OF disassembly and partially inspired - * by Rockchip patent +/* Raw nand handling functions based on OF disassembly and partially inspired + * by Rockchip patent. + * + * This was written as documentation while the FTL was still unknown and sat + * under `#if 0` for years. The Scheme A FTL is now reconstructed + * (ftl-scheme-a.c) and needs exactly what this provides: chip detection and + * the geometry derived from it, plus chip select. Enabled. */ -#define MAX_FLASH_NUM 4 +/* MAX_FLASH_NUM now in nand-target.h */ #define CMD_READ_STATUS 0x70 #define CMD_RESET 0xFF #define CMD_READ_ID 0x90 #define READ_PAGE_CMD 0x30 -/* this is the struct OF uses */ -struct flashspec_t -{ - uint8_t cache_prog; - uint8_t mul_plane; - uint8_t interleave; - uint8_t large; - uint8_t five; - uint8_t mlc; - uint8_t vendor; - uint8_t access_time; - uint8_t sec_per_page; - uint8_t sec_per_page_raw; - uint16_t sec_per_block; - uint16_t sec_per_block_raw; - uint16_t page_per_block; - uint16_t page_per_block_raw; - - uint32_t tot_logic_sec; - uint32_t total_phy_sec; - uint32_t total_bloks; - - uint32_t cmd; - uint32_t addr; - uint32_t data; -}; +/* struct flashspec_t now lives in nand-target.h - the Scheme A FTL's flash + * primitives (flash-rk27xx.c) need the geometry flash_init() derives here. */ /* holds nand chips characteristics */ struct flashspec_t flash_spec[MAX_FLASH_NUM]; @@ -66,17 +48,10 @@ struct flashspec_t flash_spec[MAX_FLASH_NUM]; /* sum of all phy sectors in all chips */ uint32_t total_phy_sec; -enum vendor_t { - SAMSUNG, - TOSHIBA, - HYNIX, - INFINEON, - MICRON, - RENESAS, - ST -}; - -/* taken from OF */ +/* taken from OF - one entry per device_info[] row. The OF's table is + * 76 79 f1 da dc d3 d5 d7; 0xd5 was once missing here, which shifted every + * later code onto the capacity one row up and sized a 4 GiB 0xd7 part as + * 2 GiB. */ const uint8_t device_code[] = { 0x76, 0x79, @@ -84,6 +59,7 @@ const uint8_t device_code[] = { 0xda, 0xdc, 0xd3, + 0xd5, 0xd7 }; @@ -111,6 +87,36 @@ const uint32_t device_info[] = 0x800000 /* 4G, large page */ }; +/* device_code[j] is looked up and device_info[j] used - a length mismatch + * silently mis-sizes every chip after the gap */ +extern char device_tables_match[(sizeof(device_code) == + sizeof(device_info) / sizeof(device_info[0])) ? 1 : -1]; + +/* State the OF keeps across flash operations. + * + * These were referenced by flash_init() but never transcribed - the block was + * under `#if 0`, so nothing caught it. They are defined here with the values + * flash_init() assigns, which is all the current code needs: + * + * - mlc_refresh_row: row scheduled for refresh after a sector needed + * BCH_REFRESH_THRESHOLD corrected bits; 0xffffffff means "none pending". + * - flash_pend_cmd: the OF defers a program command so it can be merged with + * the next one (cache programming). Nothing issues one yet. + * - flash_read_status_cmd: READ STATUS opcode, which differs on some + * multi-plane parts. + */ +uint32_t mlc_refresh_row; + +struct flash_pend_cmd_t +{ + uint8_t valid; + uint32_t row; +}; + +struct flash_pend_cmd_t flash_pend_cmd; + +uint8_t flash_read_status_cmd; + static int flash_delay(int n) { volatile int cnt, i, j; @@ -157,6 +163,43 @@ void flash_chip_select(uint8_t chip) FMCTL = tmp; } +/* NAND bus timing for an AHB clock of `mhz`, from chip 0's access time, as + * the OF's FlashTimingCfg() computes it - the OF and the YP-CP3's NAND + * bootloader call it at every bus clock change. The value starts at 0x40, + * or 0x41 on Hynix and 0x60 on Toshiba and Micron parts, and grows by 0x20 + * (by 1 from 0x100 on) for every bus cycle the access time needs beyond + * two. On the YP-CP3's Samsung part (25 ns) at 100 MHz that is 0x60, where + * flash_init()'s 0x1081 - what the OF's FlashInit() sets before - spends + * about twice as long on every byte. */ +static void flash_timing_cfg(uint32_t mhz) +{ + const struct flashspec_t *f = &flash_spec[0]; + uint32_t wait, period, cycles; + + if (mhz == 0 || mhz >= 200) + return; + + if (f->vendor == MICRON || f->vendor == TOSHIBA) + wait = 0x60; + else if (f->vendor == HYNIX) + wait = 0x41; + else + wait = 0x40; + + period = 1000 / mhz; + cycles = (f->access_time + period - 1) / period; + + while (cycles-- > 2) + { + if (wait < 0x100) + wait += 0x20; + else + wait += 1; + } + + FMWAIT = wait; +} + void flash_init(void) { uint8_t buff[5]; /* buff for CMD_READ_ID response */ @@ -176,8 +219,8 @@ void flash_init(void) * just for reference what OF does */ flash_spec[i].cmd = 0x180E8200 + (i<<9); - flash_spec[i].addr = 0x180E204 + (i<<9); - flash_spec[i].data = 0x180E208 + (i<<9); + flash_spec[i].addr = 0x180E8204 + (i<<9); + flash_spec[i].data = 0x180E8208 + (i<<9); flash_chip_select(i); FLASH_CMD(i) = CMD_RESET; /* write cmd to flash chip */ @@ -196,7 +239,7 @@ void flash_init(void) for (j=0; j>2) - 1); /* number of dma transactions + * of transfer size bytes + * (zero based) + */ + + hdma_isr_update(HDMA_CH1_ISR_BITS, HDMA_CH1_ISR_RUN); + + HDMA_ISCNT1 = 0x07; /* slice size in transfer size units (zero base) */ + + HDMA_IPNCNTD1 = 0x01; /* page count */ + + HDMA_CON1 = ((0<<23) | /* page mode */ + (1<<22) | /* slice mode */ + (1<<21) | /* DMA enable */ + (1<<18) | /* generate interrupt */ + (0<<16) | /* on-the-fly is not supported by rk27xx */ + (5<<13) | /* transfer mode inc8 */ + (7<<9) | /* external hdreq from i2s rx */ + (1<<7) | /* fixed source address */ + (0<<5) | /* increment destination address */ + (2<<3) | /* transfer size = 32bits word */ + (0<<1) | /* command of software DMA (not relevant) */ + (1<<0)); /* hardware trigger DMA mode */ +} + +static void rec_dma_done(void) +{ + void *start; + size_t size; + + if (pcm_rec_dma_complete_callback(PCM_DMAST_OK, &start, &size)) + { + hdma_i2s_rec_transfer(start, size); + pcm_rec_dma_status_callback(PCM_DMAST_STARTED); + } +} + +void pcm_rec_lock(void) +{ + int old = disable_irq_save(); + ++rec_locked; + restore_irq(old); +} + +void pcm_rec_unlock(void) +{ + int old = disable_irq_save(); + + if (--rec_locked == 0 && rec_pending) + { + rec_pending = false; + rec_dma_done(); + } + + restore_irq(old); +} + +void pcm_rec_dma_stop(void) +{ + HDMA_CON1 = 0x00; + hdma_isr_update(HDMA_CH1_ISR_BITS, HDMA_CH1_ISR_STOP); + rec_pending = false; + + I2S_OPR = I2S_OPR_PLAY; +} + +void pcm_rec_dma_start(void *addr, size_t size) +{ + pcm_rec_dma_stop(); + + I2S_OPR = I2S_OPR_PLAY | (1<<16); /* reset Rx */ + I2S_OPR = I2S_OPR_RECORD; + + hdma_i2s_rec_transfer(addr, size); +} + +void pcm_rec_dma_init(void) +{ + pcm_rec_dma_stop(); +} + +void pcm_rec_dma_close(void) +{ + pcm_rec_dma_stop(); +} + +const void * pcm_rec_dma_get_peak_buffer(void) +{ + /* where the DMA writes now */ + return (const void *)(HDMA_CDST1 & ~3); +} +#endif /* HAVE_RECORDING */ diff --git a/firmware/target/arm/rk27xx/rk27generic/lcd-rk27generic.c b/firmware/target/arm/rk27xx/rk27generic/lcd-rk27generic.c index b40f2860d7..bb3998b60c 100644 --- a/firmware/target/arm/rk27xx/rk27generic/lcd-rk27generic.c +++ b/firmware/target/arm/rk27xx/rk27generic/lcd-rk27generic.c @@ -26,33 +26,22 @@ #include "spfd5420a.h" #include "lcdif-rk27xx.h" -/* TODO: convert to udelay() */ -static inline void delay_nop(int cycles) -{ - asm volatile ("1: subs %[n], %[n], #1 \n\t" - " bne 1b" - : - : [n] "r" (cycles)); -} - - -/* not tested */ static void lcd_sleep(bool sleep) { if (sleep) { /* enter sleep mode */ lcd_write_reg(DISPLAY_CTRL1, 0x0170); - delay_nop(50); + udelay(5); lcd_write_reg(DISPLAY_CTRL1, 0x0000); - delay_nop(50); + udelay(5); lcd_write_reg(PWR_CTRL1, 0x14B4); } else { /* return to normal operation */ lcd_write_reg(PWR_CTRL1, 0x14B0); - delay_nop(50); + udelay(5); lcd_write_reg(DISPLAY_CTRL1, 0x0173); } @@ -64,9 +53,9 @@ void lcd_display_init(void) unsigned int x, y; lcd_write_reg(RESET, 0x0001); - delay_nop(10000); + udelay(200); lcd_write_reg(RESET, 0x0000); - delay_nop(10000); + udelay(200); lcd_write_reg(IF_ENDIAN, 0x0000); /* order of receiving data */ lcd_write_reg(DRIVER_OUT_CTRL, 0x0000); lcd_write_reg(ENTRY_MODE, 0x1038); @@ -88,10 +77,10 @@ void lcd_display_init(void) lcd_write_reg(PART2_END, 0x0000); lcd_write_reg(PANEL_IF_CTRL1, 0x0011); - delay_nop(10000); + udelay(200); lcd_write_reg(PANEL_IF_CTRL2, 0x0202); lcd_write_reg(PANEL_IF_CTRL3, 0x0300); - delay_nop(10000); + udelay(200); lcd_write_reg(PANEL_IF_CTRL4, 0x021E); lcd_write_reg(PANEL_IF_CTRL5, 0x0202); lcd_write_reg(PANEL_IF_CTRL6, 0x0100); @@ -119,20 +108,20 @@ void lcd_display_init(void) lcd_write_reg(DISPLAY_CTRL1, 0x0001); lcd_write_reg(PWR_CTRL6, 0x0001); lcd_write_reg(PWR_CTRL7, 0x0060); - delay_nop(50000); + udelay(1000); lcd_write_reg(PWR_CTRL1, 0x16B0); - delay_nop(10000); + udelay(200); lcd_write_reg(PWR_CTRL2, 0x0147); - delay_nop(10000); + udelay(200); lcd_write_reg(PWR_CTRL3, 0x0117); - delay_nop(10000); + udelay(200); lcd_write_reg(PWR_CTRL4, 0x2F00); - delay_nop(50000); + udelay(1000); lcd_write_reg(VCOM_HV2, 0x0000); /* src 0x0090 */ - delay_nop(10000); + udelay(200); lcd_write_reg(VCOM_HV1, 0x0008); /* src 0x000A */ lcd_write_reg(PWR_CTRL3, 0x01BE); - delay_nop(10000); + udelay(200); /* addresses setup */ lcd_write_reg(WINDOW_H_START, 0x0000); @@ -144,11 +133,11 @@ void lcd_display_init(void) /* display on */ lcd_write_reg(DISPLAY_CTRL1, 0x0021); - delay_nop(40000); + udelay(800); lcd_write_reg(DISPLAY_CTRL1, 0x0061); - delay_nop(100000); + udelay(2000); lcd_write_reg(DISPLAY_CTRL1, 0x0173); - delay_nop(300000); + udelay(6000); /* clear screen */ diff --git a/firmware/target/arm/rk27xx/rkw-loader.c b/firmware/target/arm/rk27xx/rkw-loader.c index 9b50e1b187..c7f694e8a0 100644 --- a/firmware/target/arm/rk27xx/rkw-loader.c +++ b/firmware/target/arm/rk27xx/rkw-loader.c @@ -19,6 +19,7 @@ ****************************************************************************/ #include +#include #include "config.h" #include "loader_strerror.h" #include "rkw-loader.h" @@ -26,6 +27,82 @@ #include "file.h" #include "panic.h" +#ifdef BOOTLOADER +/* Before the NAND bootloader jumps to an image it writes three words at + * the address in header field 0x14: a magic, its own version and which + * copy of the image it loaded. The OF never initialises them; it reports + * the version over USB and counts its reboots in the third word. When we + * start the OF ourselves we pass on what the NAND bootloader gave us, so + * the OF sees the same as when it is started directly. + */ +#define RKW_HANDOFF_MAGIC 0x03df479a +/* written over the magic once the words have been read */ +#define RKW_HANDOFF_CONSUMED 0x123456ad + +/* The last 12 bytes of DRAM. tools/rkw.c puts this address in every + * Rockbox RKW, so the NAND bootloader leaves our words here. + */ +#define RKW_HANDOFF_ADDR (0x60000000 + MEMORYSIZE * 0x100000 - 12) +#if MEMORYSIZE != 16 +#error "tools/rkw.c assumes 16 MB of DRAM, update RKW_HANDOFF_ADDR there" +#endif + +struct rkw_handoff_t { + uint32_t magic; + uint32_t version; + uint32_t source; +}; + +/* The NAND bootloader's words, read once: loading an image may overwrite + * them, and a reboot keeps DRAM, so they are marked consumed. + */ +static struct rkw_handoff_t *rkw_own_handoff(void) +{ + static struct rkw_handoff_t own; + static bool read_done = false; + volatile struct rkw_handoff_t *h = + (volatile struct rkw_handoff_t *)RKW_HANDOFF_ADDR; + + if (!read_done) + { + if (h->magic == RKW_HANDOFF_MAGIC) + { + own.version = h->version; + own.source = h->source; + } + else + { + /* not started by the NAND bootloader, e.g. over USB */ + own.version = 0; + own.source = 0; + } + own.magic = RKW_HANDOFF_MAGIC; + h->magic = RKW_HANDOFF_CONSUMED; + read_done = true; + } + + return &own; +} + +/* Hand the words on to an image loaded at its own address, when its + * header points past the image and below us. + */ +static void rkw_pass_handoff(const struct rkw_header_t *hdr, + const unsigned char *buf, int len, + int buffer_size) +{ + uintptr_t addr = hdr->handoff; + uintptr_t lo = (uintptr_t)buf + len; + uintptr_t hi = (uintptr_t)buf + buffer_size; + + if ((uintptr_t)buf != hdr->load_address || (addr & 3) || + addr < lo || addr > hi - sizeof(struct rkw_handoff_t)) + return; + + *(struct rkw_handoff_t *)addr = *rkw_own_handoff(); +} +#endif /* BOOTLOADER */ + /* loosely based on load_firmware() * on success we return size of loaded image * on error we return negative value which can be deciphered by means @@ -116,6 +193,11 @@ int load_rkw(unsigned char* buf, const char* firmware, int buffer_size) goto end; } +#ifdef BOOTLOADER + /* before the image can overwrite them */ + rkw_own_handoff(); +#endif + /* skip header */ lseek(fd, sizeof(rkw_info), SEEK_SET); @@ -141,6 +223,10 @@ int load_rkw(unsigned char* buf, const char* firmware, int buffer_size) } } +#ifdef BOOTLOADER + rkw_pass_handoff(&rkw_info, buf, len, buffer_size); +#endif + ret = len; end: close(fd); diff --git a/firmware/target/arm/rk27xx/sd-rk27xx.c b/firmware/target/arm/rk27xx/sd-rk27xx.c index 4f9118a58d..36cea2068d 100644 --- a/firmware/target/arm/rk27xx/sd-rk27xx.c +++ b/firmware/target/arm/rk27xx/sd-rk27xx.c @@ -142,7 +142,8 @@ static void mmu_buff_reset(void) static inline bool card_detect_target(void) { -#if defined(RK27_GENERIC) || defined(IHIFI770) || defined(IHIFI770C) || defined(IHIFI800) +#if defined(RK27_GENERIC) || defined(IHIFI770) || defined(IHIFI770C) || defined(IHIFI800) || \ + defined(SAMSUNG_YPCP3) /* PC7, active low */ return !(GPIO_PCDR & 0x80); #elif defined(HM60X) || defined(HM801) @@ -322,10 +323,11 @@ static int sd_init_card(void) if (!sd_wait_card_busy()) return -21; - /* CMD6 */ - if(!send_cmd(SD_SWITCH_FUNC, 0x80fffff1, RES_R1, &response)) - return -8; - sleep(HZ/10); + /* No CMD6 switch to high-speed mode: this is an SD 1.01 host, with a + * card clock of at most 25 MHz, and a card switched to high speed does + * not take the data it drives - every write failed, the card waiting in + * receive-data state for a block it never saw, while reads worked. The + * original firmware leaves the card at default speed too. */ /* Card back to full speed 25MHz*/ SD_CTRL = (SD_CTRL & ~0x7FF); @@ -463,12 +465,15 @@ int sd_read_sectors(IF_MD(int drive,) sector_t start, int count, int cnt, ret = 0; unsigned char *dst; - mutex_lock(&sd_mtx); - enable_controller(true); - + /* Checked before taking the lock: returning from after it left sd_mtx + * held and the controller powered. With no card numblocks is 0, so + * that was every request. */ if (count <= 0 || start + count > card_info.numblocks) return -1; + mutex_lock(&sd_mtx); + enable_controller(true); + if(!(card_info.ocr & (1<<30))) start <<= 9; /* not SDHC */ @@ -601,12 +606,15 @@ int sd_write_sectors(IF_MD(int drive,) sector_t start, int count, unsigned char *src; /* bool card_selected = false; */ - mutex_lock(&sd_mtx); - enable_controller(true); - + /* Checked before taking the lock: returning from after it left sd_mtx + * held and the controller powered. With no card numblocks is 0, so + * that was every request. */ if (count <= 0 || start + count > card_info.numblocks) return -1; + mutex_lock(&sd_mtx); + enable_controller(true); + if(!(card_info.ocr & (1<<30))) start <<= 9; /* not SDHC */ diff --git a/firmware/target/arm/rk27xx/system-rk27xx.c b/firmware/target/arm/rk27xx/system-rk27xx.c index 0d71915a00..8ca7d3d25f 100644 --- a/firmware/target/arm/rk27xx/system-rk27xx.c +++ b/firmware/target/arm/rk27xx/system-rk27xx.c @@ -23,6 +23,7 @@ #include "panic.h" #include "button.h" #include "system-target.h" +#include "lcdif-rk27xx.h" #define default_interrupt(name) \ extern __attribute__((weak,alias("UIRQ"))) void name (void) @@ -116,20 +117,8 @@ void system_init(void) WDTCON &= ~(1<<3); #ifndef BOOTLOADER - /* SDRAM tweaks. Note this assumes 100MHz AHB+SDRAM clock. */ - -#if !(defined(HM60X) || defined(HM801)) - MCSDR_MODE = (3<<4)|3; /* CAS=3, burst=8(2^3) -- Safe but slower */ -#else - MCSDR_MODE = (2<<4)|3; /* CAS=2, burst=8(2^3) -- Ideal but causes startup issues on (some?) IHIFI devices */ -#endif - - MCSDR_T_REF = (125*100) >> 3; /* 125/8 = 15.625 autorefresh interval */ - MCSDR_T_RFC = (64*100) / 1000; /* autorefresh period */ - MCSDR_T_RP = 1; /* precharge period */ - MCSDR_T_RCD = 1; /* active to RD/WR delay */ - - /* turn off clock for unused modules */ + /* turn off clock for unused modules - not the NAND controller's: the + * FTL uses it, and a register access with its HCLK gated aborts */ SCU_CLKCFG |= CLKCFG_WDT | /* WDT pclk */ CLKCFG_RTC | /* RTC pclk */ CLKCFG_HSADC | /* HS_ADC clock */ @@ -140,7 +129,6 @@ void system_init(void) CLKCFG_VIP | /* VIP clock */ CLKCFG_HCLK_VIP | /* VIP HCLK */ CLKCFG_LCDC | /* LCDC clock */ - CLKCFG_NAND | /* NAND HCLK */ CLKCFG_UHC | /* USB host HCLK */ CLKCFG_DSP | /* DSP clock */ CLKCFG_OTP; /* OTP clock (dunno what it is */ @@ -173,12 +161,6 @@ void system_exception_wait(void) while(!button_read_device()); } -int system_memory_guard(int newmode) -{ - (void)newmode; - return 0; -} - /* usecs may be at most 2^32/200 (~21 seconds) for 200MHz max cpu freq */ void udelay(unsigned usecs) { @@ -191,34 +173,28 @@ void udelay(unsigned usecs) cycles_per_usec = (CPUFREQ_NORMAL + 999999) / 1000000; } - delay = (usecs * cycles_per_usec) / 5; + delay = (usecs * cycles_per_usec) / 4; asm volatile( "1: subs %0, %0, #1 \n" /* 1 cycle */ - " nop \n" /* 1 cycle */ " bne 1b \n" /* 3 cycles */ - : : "r"(delay) + : "+r"(delay) /* modified by the loop: in/out */ + : + : "cc" /* flags clobbered */ ); } -static void cache_invalidate_way(int way) -{ - /* Issue invalidata way command to the cache controler */ - CACHEOP = ((way<<31)|0x2); - - /* wait for invalidate process to complete */ - while (CACHEOP & 0x03); -} - +/* No whole-cache invalidate at run time, as in the original firmware: it + * invalidates the ways once, at power-on with the cache off (crt0.S), and + * after that only single lines. Invalidating the ways while running from + * cached SDRAM crashed depending on where code happened to lie - with the + * cache on in the poll loop, and with it off later, as code was fetched + * again. None is needed: the cache is unified and write-through, so what + * the CPU writes, code included, is in memory and in any cached copy, and + * every DMA into memory discards the lines of its own buffer first + * (commit_discard_dcache_range()). */ void commit_discard_idcache(void) { - int old_irq = disable_irq_save(); - - cache_invalidate_way(0); - - cache_invalidate_way(1); - - restore_irq(old_irq); } void commit_discard_dcache (void) __attribute__((alias("commit_discard_idcache"))); @@ -240,12 +216,8 @@ void commit_discard_dcache_range (const void *base, unsigned int size) #if !defined(BOOTLOADER) && defined(HAVE_ADJUSTABLE_CPU_FREQ) static inline void set_sdram_timing(int ahb_freq) { -#if 1 - if (ahb_freq > 100000000) - MCSDR_MODE = (3<<4)|3; /* CAS=3, burst=8(2^3) */ - else - MCSDR_MODE = (2<<4)|3; /* CAS=2, burst=8(2^3) */ -#endif + /* refresh only: the mode register - CAS latency - stays as the boot set + * it (see system_init()) */ MCSDR_T_REF = (125*ahb_freq/1000000) >> 3; MCSDR_T_RFC = (64*ahb_freq/1000000)/1000; } @@ -261,6 +233,9 @@ void set_cpu_frequency(long frequency) if (frequency == CPUFREQ_MAX) { + /* the LCD strobes take twice the clocks at twice the clock */ + lcdif_set_bus_timing(true); + /* PLL set to 200 Mhz * PLL:ARM = 1:1 * ARM:AHB = 2:1 @@ -294,6 +269,8 @@ void set_cpu_frequency(long frequency) SCU_DIVCON1 &= ~1; set_sdram_timing(CPUFREQ_NORMAL); + + lcdif_set_bus_timing(false); } cpu_frequency = frequency; diff --git a/firmware/target/arm/rk27xx/usb-drv-rk27xx.c b/firmware/target/arm/rk27xx/usb-drv-rk27xx.c index ee46c514fb..0f7637ebf9 100644 --- a/firmware/target/arm/rk27xx/usb-drv-rk27xx.c +++ b/firmware/target/arm/rk27xx/usb-drv-rk27xx.c @@ -20,6 +20,7 @@ #include "config.h" #include "usb.h" +#include "usb-rk27xx.h" #include "usb_drv.h" #include "cpu.h" @@ -30,6 +31,7 @@ #include "usb_ch9.h" #include "usb_core.h" #include +#include #include "power.h" #define LOGF_ENABLE @@ -52,10 +54,15 @@ struct endpoint_t const int type; /* EP type */ const int dir; /* DIR_IN/DIR_OUT */ volatile unsigned long *stat; /* RXSTAT/TXSTAT register */ + bool enabled; /* set up by usb_drv_ep_init() */ volatile void *buf; /* tx/rx buffer address */ volatile int len; /* size of the transfer (bytes) */ volatile int cnt; /* number of bytes transfered/received */ volatile bool block; /* flag indicating that transfer is blocking */ + volatile bool busy; /* a transfer is armed and not yet finished */ + volatile bool zlp; /* IN: end a max-packet-sized transfer with a + * zero length packet */ + volatile int result; /* 0 = completed, -1 = cancelled */ struct semaphore complete; /* semaphore for blocking transfers */ }; @@ -72,7 +79,8 @@ struct endpoint_t #define DMAINLMADDR(endp) *(4 + (endp)->stat) #define ENDPOINT(num, type, dir, reg) \ - {num, USB_ENDPOINT_XFER_##type, USB_DIR_##dir, reg, NULL, 0, 0, true, {{0, 0}, 0, 0}} + {num, USB_ENDPOINT_XFER_##type, USB_DIR_##dir, reg, false, NULL, 0, 0, \ + true, false, false, 0, {{0, 0}, 0, 0}} static struct endpoint_t ctrlep[2] = { @@ -100,25 +108,82 @@ static struct endpoint_t endpoints[16] = ENDPOINT(15, INT, IN, &TX15STAT), /* IIN15 */ }; -struct usb_drv_ep_spec usb_drv_ep_specs[16]; /* filled in usb_drv_startup */ -uint8_t usb_drv_ep_specs_flags = 0; - static volatile bool set_address = false; static volatile bool set_configuration = false; #undef ENDPOINT -static void setup_received(void) +/* EP0 control transfers: the USB core runs the control request state + * machine. The driver hands it each SETUP and reports the end of every EP0 + * transfer the core arms, the status stages included. + * + * The UDC completes SET_ADDRESS and SET_CONFIGURATION by itself: no SETUP + * reaches the driver for either, and udc_helper() reports them to the core + * from DEV_INFO instead. */ + +/* wLength of the latest request: an answer is never longer, and one shorter + * that fills whole packets ends with a zero length packet */ +static uint16_t ep0_wlength; + +/* status stage OUT lands here; the host sends no data, but DMA wants a + * target */ +static uint8_t ep0_dummy[64] __attribute__((aligned(32))); + +/* drop whatever EP0 transfer is in flight, both directions, silently */ +static void ep0_abort(void) { - static uint32_t setup_data[2]; - logf("udc: setup"); + ctrlep[DIR_IN].busy = false; + ctrlep[DIR_OUT].busy = false; - /* copy setup data from packet */ - setup_data[0] = SETUP1; - setup_data[1] = SETUP2; + TX0CON |= TXNAK | TXCLR; + TX0CON &= ~TXCLR; + RX0CON |= RXNAK | RXCLR; + RX0CON &= ~RXCLR; +} - /* pass setup data to the upper layer */ - usb_core_setup_received((struct usb_ctrlrequest*)setup_data); +/* End the EP0 transfers the core is waiting on as failed, so that its state + * machine gets back to taking requests. OUT first: in a control read the + * core expects the status OUT to end before the data IN. */ +static void ep0_fail_transfers(void) +{ + bool out = ctrlep[DIR_OUT].busy; + bool in = ctrlep[DIR_IN].busy; + + ep0_abort(); + if(out) + usb_core_transfer_complete(EP_CONTROL, USB_DIR_OUT, -1, 0); + if(in) + usb_core_transfer_complete(EP_CONTROL, USB_DIR_IN, -1, 0); +} + +static void ep0_setup_received(void) +{ + /* the two SETUP registers are the request's 8 bytes */ + union + { + uint32_t words[2]; + struct usb_ctrlrequest req; + } setup; + + setup.words[0] = SETUP1; + setup.words[1] = SETUP2; + logf("udc: setup %08lx %08lx", setup.words[0], setup.words[1]); + + /* Right after connect the UDC reports one SETUP with both registers + * zero. No host sends that - a host-to-device GET_STATUS - and answering + * it puts two bytes nobody asked for into the EP0 FIFO. */ + if(setup.words[0] == 0 && setup.words[1] == 0) + return; + + /* a SETUP ends a protocol stall */ + TX0CON &= ~TXSTALL; + RX0CON &= ~RXSTALL; + + /* a transfer still in flight belongs to a request the host abandoned */ + ep0_fail_transfers(); + + ep0_wlength = setup.req.wLength; + usb_core_setup_received(&setup.req); } static int max_pkt_size(struct endpoint_t *endp) @@ -135,11 +200,13 @@ static int max_pkt_size(struct endpoint_t *endp) static void ep_write(struct endpoint_t *endp) { int xfer_size = MIN(max_pkt_size(endp), endp->cnt); - unsigned int timeout = current_tick + HZ/10; - while(TXBUF(endp) & TXFULL) /* TXFULL flag */ + /* Bounded by iterations, not by current_tick: this also runs in the + * interrupt handler (the next packet of a transfer), where the tick + * never advances and a tick timeout spins forever. */ + for(int spin = 0; TXBUF(endp) & TXFULL; spin++) /* TXFULL flag */ { - if(TIME_AFTER(current_tick, timeout)) + if(spin > 200000) break; } @@ -182,9 +249,15 @@ static void in_intr(struct endpoint_t *endp) if(txstat & TXACK) { logf("udc: ack(%d)", endp->ep_num); - /* finished ? */ - if(endp->cnt <= 0) + /* nothing armed: a transfer cancelled by a bus reset */ + if(!endp->busy) + return; + /* finished ? cnt 0 means the last packet was a full one: done, + * unless the transfer must end with a zero length packet */ + if(endp->cnt < 0 || (endp->cnt == 0 && !endp->zlp)) { + endp->busy = false; + endp->result = 0; usb_core_transfer_complete(endp->ep_num, endp->dir, 0, endp->len); /* release semaphore for blocking transfer */ if(endp->block) @@ -208,13 +281,34 @@ static void out_intr(struct endpoint_t *endp) /* check if a transfer has finished */ if(rxstat & RXACK) { + /* nothing armed: a transfer cancelled by a bus reset */ + if(!endp->busy) + return; int xfer_size = rxstat & 0xffff; endp->cnt -= xfer_size; endp->buf += xfer_size; logf("udc: ack(%d) -> %d/%d", endp->ep_num, xfer_size, endp->cnt); /* finished ? */ if(endp->cnt <= 0 || xfer_size < max_pkt_size(endp)) + { + endp->busy = false; + endp->result = 0; usb_core_transfer_complete(endp->ep_num, endp->dir, 0, endp->len); + if(endp->block) + semaphore_release(&endp->complete); + /* EP0: a status OUT while the data IN is still going means the + * host took less than was offered, which is allowed - the data + * stage is over too */ + if(endp->ep_num == 0 && ctrlep[DIR_IN].busy) + { + struct endpoint_t *in = &ctrlep[DIR_IN]; + + in->busy = false; + TX0CON |= TXNAK | TXCLR; + TX0CON &= ~TXCLR; + usb_core_transfer_complete(EP_CONTROL, USB_DIR_IN, 0, in->len); + } + } else ep_read(endp); } @@ -252,25 +346,29 @@ static void udc_helper(void) usb_core_notify_set_address(dev_info & 0x7f); } - /* SET CONFIGURATION request */ + /* SET CONFIGURATION request - cfg_number is DEV_INFO [11:8], counted + * from 0; bit 7 is DEV_EN itself */ if(!set_configuration) if(dev_info & DEV_EN) { set_configuration = true; - usb_core_notify_set_config(((dev_info >> 7) & 0xf) + 1); + usb_core_notify_set_config(((dev_info >> 8) & 0xf) + 1); } } -void usb_drv_startup(void) { - /* fill the endpoint spec table */ - usb_drv_ep_specs[0].type[DIR_OUT] = USB_ENDPOINT_XFER_CONTROL; - usb_drv_ep_specs[0].type[DIR_IN] = USB_ENDPOINT_XFER_CONTROL; - for(int ep_num = 1; ep_num < 16; ep_num++) { - int dir = endpoints[ep_num].dir; - int type = endpoints[ep_num].type; - usb_drv_ep_specs[ep_num].type[dir] = type; - usb_drv_ep_specs[ep_num].type[!dir] = USB_ENDPOINT_TYPE_NONE; - } +/* The UDC completes SET_ADDRESS and SET_CONFIGURATION without raising an + * interrupt, so udc_helper() in the interrupt handler only notices them at + * the next UDC interrupt. At first enumeration GET_MAX_LUN provides one. + * After a bus reset of a configured device (usbreset, or an error recovery) + * the host re-sends SET_CONFIGURATION and goes straight to a bulk command: + * nothing else arrives on EP0, the bulk endpoint NAKs because the class was + * never re-armed, and NAKs raise no interrupt - the device hangs until the + * host times out, resets, and hangs again. So poll from the tick as well, + * for as long as the device is not configured. */ +static void udc_tick(void) +{ + if(!set_configuration) + udc_helper(); } /* return port speed FS=0, HS=1 */ @@ -279,31 +377,82 @@ int usb_drv_port_speed(void) return (DEV_INFO & DEV_SPEED) ? 0 : 1; } +/* The UDC's endpoints come in groups of three - bulk OUT, bulk IN, + * interrupt IN: 1-3, 4-6 and so on - each endpoint with one fixed type and + * direction. An interrupt endpoint in the same group as bulk endpoints in + * use slows the bulk transfers down: every IN token the host polls it with + * and it NAKs costs the group's bulk traffic. With HID on endpoint 3 beside + * mass storage on 1 and 2, writes ran at 0.03 MB/s, and polled every 125 us + * instead of every 16 ms they all but stopped; on endpoint 6 they ran at + * 2.3 MB/s, as with no HID at all. So interrupt and bulk endpoints never + * share a group - which depends on what else is allocated, hence the + * driver's own allocator (usb_drv.h, option 2). */ +void usb_drv_ep_reset_alloc_ctx(struct usb_drv_ep_alloc_ctx* ctx) +{ + memset(ctx->type, -1, sizeof(ctx->type)); + memset(ctx->max_packet_size, 0, sizeof(ctx->max_packet_size)); +} + +bool usb_drv_ep_allocate(struct usb_drv_ep_alloc_ctx* ctx, int ep, int type, + int max_packet_size) +{ + int ep_num = EP_NUM(ep); + int dir = EP_DIR(ep); + struct endpoint_t *endp = &endpoints[ep_num]; + + if(ep_num == 0 || endp->type != type || + endp->dir != (dir == DIR_IN ? USB_DIR_IN : USB_DIR_OUT)) + return false; + + int first = (ep_num - 1) / 3 * 3 + 1; + for(int i = first; i < first + 3 && i < USB_NUM_ENDPOINTS; i++) + { + for(int d = 0; d < 2; d++) + { + if(ctx->type[i][d] != -1 && ctx->type[i][d] != type) + return false; + } + } + + ctx->type[ep_num][dir] = type; + ctx->max_packet_size[ep_num][dir] = max_packet_size; + return true; +} + +/* one-time init: nothing to do, the endpoints are fixed in endpoints[] */ +void usb_drv_startup(void) +{ +} + void usb_drv_ep_init(const struct usb_drv_ep_alloc_ctx* ctx, int ep) { /* FIXME: support max packet size override */ (void)ctx; - const int epnum = EP_NUM(ep); - const int epdir = EP_DIR(ep); + int ep_num = EP_NUM(ep); + struct endpoint_t *endp = &endpoints[ep_num]; - struct endpoint_t *endp = &endpoints[epnum]; - - if(epdir == DIR_IN) - TXCON(endp) = (epnum << 8) | TXEPEN | TXNAK | TXACKINTEN | TXCFINTE; + if(EP_DIR(ep) == DIR_IN) + TXCON(endp) = (ep_num << 8) | TXEPEN | TXNAK | TXACKINTEN | TXCFINTE; else - RXCON(endp) = (epnum << 8) | RXEPEN | RXNAK | RXACKINTEN | RXCFINTE | RXERRINTEN; - EN_INT |= 1 << (epnum + 7); + RXCON(endp) = (ep_num << 8) | RXEPEN | RXNAK | RXACKINTEN | RXCFINTE | + RXERRINTEN; + endp->enabled = true; + EN_INT |= 1 << (ep_num + 7); + logf("add: ep%d %s", ep_num, XFER_DIR_STR(EP_DIR(ep) == DIR_IN)); } void usb_drv_ep_deinit(const struct usb_drv_ep_alloc_ctx* ctx, int ep) { (void)ctx; - int num = EP_NUM(ep); + int ep_num = EP_NUM(ep); + + logf("rel: ep%d", ep_num); + endpoints[ep_num].enabled = false; /* disable interrupt from this endpoint */ - EN_INT &= ~(1 << (num + 7)); + EN_INT &= ~(1 << (ep_num + 7)); } /* Set the address (usually it's in a register). @@ -324,24 +473,57 @@ static int _usb_drv_send(int endpoint, void *ptr, int length, bool block) struct endpoint_t *ep; int ep_num = EP_NUM(endpoint); + bool zlp = false; + if (ep_num == 0) + { ep = &ctrlep[DIR_IN]; + /* never send more than asked for, and end a short answer that fills + * whole packets with a zero length packet */ + if(length > ep0_wlength) + length = ep0_wlength; + zlp = length > 0 && length < ep0_wlength && length % 64 == 0; + } else ep = &endpoints[ep_num]; /* for send transfers, make sure the data is committed */ - commit_discard_dcache_range(ptr, length); + if(length) + commit_discard_dcache_range(ptr, length); + + /* drop a release left over from a transfer that ended (or was + * cancelled) after its waiter had already given up */ + if(block) + semaphore_wait(&ep->complete, TIMEOUT_NOBLOCK); + + /* the interrupt handler works on these fields too */ + int oldlevel = disable_irq_save(); ep->buf = ptr; ep->len = ep->cnt = length; ep->block = block; - + ep->zlp = zlp; + ep->busy = true; + ep->result = 0; ep_write(ep); + restore_irq(oldlevel); - /* wait for transfer to end */ - if(block) - semaphore_wait(&ep->complete, TIMEOUT_BLOCK); + if(!block) + return 0; - return 0; + /* wait for transfer to end - but not forever: a host that gave up on + * the transfer would otherwise wedge the calling thread (the USB + * thread, for control responses) for good */ + if(semaphore_wait(&ep->complete, HZ) == OBJ_WAIT_TIMEDOUT) + { + oldlevel = disable_irq_save(); + bool pending = ep->busy; + ep->busy = false; + restore_irq(oldlevel); + if(pending) + return -1; + } + + return ep->result; } /* Setup a send transfer. (blocking) */ @@ -364,24 +546,74 @@ int usb_drv_recv_nonblocking(int endpoint, void* ptr, int length) int ep_num = EP_NUM(endpoint); if(ep_num == 0) + { ep = &ctrlep[DIR_OUT]; + if(ptr == NULL) + ptr = ep0_dummy; + } else ep = &endpoints[ep_num]; /* for recv, discard the cache lines related to the buffer */ - commit_discard_dcache_range(ptr, length); + if(length) + commit_discard_dcache_range(ptr, length); + + /* the interrupt handler works on these fields too */ + int oldlevel = disable_irq_save(); ep->buf = ptr; ep->len = ep->cnt = length; + ep->block = false; + ep->busy = true; + ep->result = 0; ep_read(ep); + restore_irq(oldlevel); return 0; } -/* Kill all transfers. Usually you need to set a bit for each endpoint - * and flush fifos. You should also call the completion handler with - * error status for everything - */ +static void cancel_transfer(struct endpoint_t *endp) +{ + if(!endp->busy) + return; + + endp->busy = false; + endp->result = -1; + endp->cnt = 0; + /* wake a blocked sender - it sees result -1 */ + if(endp->block) + semaphore_release(&endp->complete); +} + +/* Kill all transfers: after a bus reset nothing armed before it will ever + * complete. Blocked senders are woken with an error. Class drivers are not + * called - the host re-configures after a reset, and init_connection() + * re-arms them from scratch. EP0 is not touched: a bus reset drops its + * transfers with ep0_abort(), and the core resets its own EP0 state. */ void usb_drv_cancel_all_transfers(void) { + int oldlevel = disable_irq_save(); + + for(int ep_num = 1; ep_num < USB_NUM_ENDPOINTS; ep_num++) + { + struct endpoint_t *endp = &endpoints[ep_num]; + + cancel_transfer(endp); + if(!endp->enabled) + continue; + + /* NAK and flush whatever is left in the FIFO */ + if(endp->dir == USB_DIR_IN) + { + TXCON(endp) |= TXNAK | TXCLR; + TXCON(endp) &= ~TXCLR; + } + else + { + RXCON(endp) |= RXNAK | RXCLR; + RXCON(endp) &= ~RXCLR; + } + } + + restore_irq(oldlevel); } /* Set test mode, you can forget that for now, usually it's sufficient @@ -392,10 +624,20 @@ void usb_drv_set_test_mode(int mode) (void)mode; } +/* endpoints[0] is a stub without registers: EP0 lives in ctrlep[] */ +static struct endpoint_t *stall_ep(int endpoint, bool in) +{ + int ep_num = EP_NUM(endpoint); + + if(ep_num == 0) + return &ctrlep[in ? DIR_IN : DIR_OUT]; + return &endpoints[ep_num]; +} + /* Check if endpoint is in stall state */ bool usb_drv_stalled(int endpoint, bool in) { - struct endpoint_t *endp = &endpoints[EP_NUM(endpoint)]; + struct endpoint_t *endp = stall_ep(endpoint, in); if(in) return !!(TXCON(endp) & TXSTALL); @@ -406,7 +648,7 @@ bool usb_drv_stalled(int endpoint, bool in) /* Stall the endpoint. Usually set a flag in the controller */ void usb_drv_stall(int endpoint, bool stall, bool in) { - struct endpoint_t *endp = &endpoints[EP_NUM(endpoint)]; + struct endpoint_t *endp = stall_ep(endpoint, in); if(in) { if(stall) @@ -423,20 +665,61 @@ void usb_drv_stall(int endpoint, bool stall, bool in) } } -/* one time init (once per connection) - basicaly enable usb core */ -void usb_drv_init(void) +/* Transfer-completion semaphores start fresh at every stack init. Never from + * the interrupt handler: re-initialising a semaphore a thread is blocked on + * loses that thread for good - a bus reset cancels transfers instead. */ +static void udc_reset_semaphores(void) { /* init semaphore of ep0 */ semaphore_init(&ctrlep[DIR_OUT].complete, 1, 0); semaphore_init(&ctrlep[DIR_IN].complete, 1, 0); + ctrlep[DIR_OUT].busy = ctrlep[DIR_IN].busy = false; for(int ep_num = 1; ep_num < USB_NUM_ENDPOINTS; ep_num++) + { semaphore_init(&endpoints[ep_num].complete, 1, 0); + endpoints[ep_num].busy = false; + } +} + +/* one time init (once per connection) - basicaly enable usb core */ +void usb_drv_init(void) +{ + udc_reset_semaphores(); + tick_add_task(udc_tick); + + /* enable USB interrupts in interrupt controller - here, as + * usb_drv_exit() disables them at every disconnect */ + INTC_IMR |= IRQ_ARM_UDC; + INTC_IECR |= IRQ_ARM_UDC; +} + +/* Present ourselves to the host. Called by usb_enable() once usb_core_init() + * has finished - not from usb_drv_init(), which usb_core_init() calls FIRST, + * before the class drivers are set up and before it sets its own state. A + * host quick enough to enumerate in that window had its requests handled + * against state usb_core_init() then overwrote: the descriptor read timed + * out, and whether it did depended on timing. + * + * Connecting only on CONN_INTR, as the interrupt handler does, needs a + * cable-insert edge after the stack is up - and there is none when the cable + * was already in: booting with it plugged, or taking the controller over + * from the ROM loader or hwstub, which leaves it enumerated as a different + * device the host has no reason to re-enumerate. So drop off the bus, reset + * the PHY and reconnect: the host sees a fresh device either way. */ +void usb_drv_connect(void) +{ + DEV_CTL &= ~DEV_SOFT_CN; + udelay(20000); + udc_phy_reset(); + udelay(10000); + udc_soft_connect(); } /* turn off usb core */ void usb_drv_exit(void) { + tick_remove_task(udc_tick); DEV_CTL = DEV_SELF_PWR; /* disable USB interrupts in interrupt controller */ @@ -487,14 +770,18 @@ void INT_UDC(void) set_address = false; set_configuration = false; + + /* before any SETUP in this same interrupt is handled: whatever + * was in flight is gone, including the control request */ + usb_drv_cancel_all_transfers(); + ep0_abort(); + usb_core_bus_reset(); } /* This needs to be processed AFTER usb reset */ udc_helper(); - if(intsrc & SETUP_INTR) /* setup interrupt */ - { - setup_received(); - } + /* EP0 completions before a SETUP: when both are pending, the + * completion belongs to the older request, which the SETUP ends */ if(intsrc & IN0_INTR) { /* EP0 IN done */ @@ -505,10 +792,9 @@ void INT_UDC(void) /* EP0 OUT done */ out_intr(&ctrlep[DIR_OUT]); } - if(intsrc & USBRST_INTR) + if(intsrc & SETUP_INTR) /* setup interrupt */ { - /* usb reset */ - usb_drv_init(); + ep0_setup_received(); } if(intsrc & RESUME_INTR) { diff --git a/firmware/target/arm/rk27xx/usb-rk27xx.c b/firmware/target/arm/rk27xx/usb-rk27xx.c index 4ac4f81c32..6ac04e41b5 100644 --- a/firmware/target/arm/rk27xx/usb-rk27xx.c +++ b/firmware/target/arm/rk27xx/usb-rk27xx.c @@ -31,10 +31,6 @@ int usb_status = USB_EXTRACTED; void usb_init_device(void) { - /* enable UDC interrupt */ - INTC_IMR |= (1<<16); - INTC_IECR |= (1<<16); - EN_INT = EN_SUSP_INTR | /* Enable Suspend Interrupt */ EN_RESUME_INTR | /* Enable Resume Interrupt */ EN_USBRST_INTR | /* Enable USB Reset Interrupt */ @@ -54,10 +50,16 @@ void usb_attach(void) usb_enable(true); } +void usb_drv_connect(void); /* usb-drv-rk27xx.c */ + void usb_enable(bool on) { if(on) + { usb_core_init(); + /* attach only once the core is fully set up - see usb_drv_connect() */ + usb_drv_connect(); + } else usb_core_exit(); } diff --git a/firmware/target/arm/rk27xx/ypcp3/button-target.h b/firmware/target/arm/rk27xx/ypcp3/button-target.h new file mode 100644 index 0000000000..27cd5dae5b --- /dev/null +++ b/firmware/target/arm/rk27xx/ypcp3/button-target.h @@ -0,0 +1,43 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2026 by Marcin Bukat + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#ifndef _BUTTON_TARGET_H_ +#define _BUTTON_TARGET_H_ + +/* The YP-CP3 has the YP-R0's key set - a five-way pad, Back, Menu, the + * Rec/User key and Power - so it shares that keypad (SAMSUNG_YPR0_PAD) and + * its keymaps; the codes are the YP-R0's. */ +#define BUTTON_UP 0x00000001 +#define BUTTON_DOWN 0x00000002 +#define BUTTON_LEFT 0x00000004 +#define BUTTON_RIGHT 0x00000008 +#define BUTTON_USER 0x00000010 +#define BUTTON_MENU 0x00000020 +#define BUTTON_BACK 0x00000040 +#define BUTTON_POWER 0x00000080 +#define BUTTON_SELECT 0x00000100 + +#define BUTTON_MAIN (BUTTON_UP | BUTTON_DOWN | BUTTON_LEFT | \ + BUTTON_RIGHT | BUTTON_USER | BUTTON_MENU | \ + BUTTON_BACK | BUTTON_POWER | BUTTON_SELECT) + +#define POWEROFF_BUTTON BUTTON_POWER +#define POWEROFF_COUNT 30 + +#endif /* _BUTTON_TARGET_H_ */ diff --git a/firmware/target/arm/rk27xx/ypcp3/button-ypcp3.c b/firmware/target/arm/rk27xx/ypcp3/button-ypcp3.c new file mode 100644 index 0000000000..204081f2b3 --- /dev/null +++ b/firmware/target/arm/rk27xx/ypcp3/button-ypcp3.c @@ -0,0 +1,100 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2026 by Marcin Bukat + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#include "config.h" +#include "system.h" +#include "button.h" +#include "adc.h" + +/* Samsung YP-CP3 keys. + * + * Eight keys sit on two resistor ladders read by the LSADC, one key per + * ladder at a time; an idle ladder reads near full scale. Measured on a + * unit, the steps are about 133 counts apart: + * + * ADC_BUTTONS (1) up 158, down 295, select 435, left 561, right 687 + * ADC_EXTRA (2) menu 155, back 292, user (Rec) 431 + * + * Each key owns the range from half a step below its reading to half a step + * above. The original firmware ignores anything from 0x2ee (750) up, which + * is where the pad's range ends here too. + * + * Power is on its own pin, GPIO C1, active high. */ + +#define POWER_PIN (1 << 1) /* GPIO C1 */ + +struct ladder_key +{ + unsigned short below; /* upper bound, exclusive */ + int button; +}; + +static const struct ladder_key pad_keys[] = +{ + { 227, BUTTON_UP }, + { 365, BUTTON_DOWN }, + { 498, BUTTON_SELECT }, + { 624, BUTTON_LEFT }, + { 750, BUTTON_RIGHT }, +}; + +static const struct ladder_key side_keys[] = +{ + { 224, BUTTON_MENU }, + { 362, BUTTON_BACK }, + { 500, BUTTON_USER }, +}; + +static int ladder_button(int channel, const struct ladder_key *keys, int n) +{ + unsigned short val = adc_read(channel); + int button = 0; + int i; + + for (i = 0; i < n && button == 0; i++) + { + if (val < keys[i].below) + { + button = keys[i].button; + } + } + + return button; +} + +void button_init_device(void) +{ + GPIO_PCCON &= ~POWER_PIN; /* input */ +} + +int button_read_device(void) +{ + int buttons = 0; + + buttons |= ladder_button(ADC_BUTTONS, pad_keys, ARRAYLEN(pad_keys)); + buttons |= ladder_button(ADC_EXTRA, side_keys, ARRAYLEN(side_keys)); + + if (GPIO_PCDR & POWER_PIN) + { + buttons |= BUTTON_POWER; + } + + return buttons; +} diff --git a/firmware/target/arm/rk27xx/ypcp3/lcd-target.h b/firmware/target/arm/rk27xx/ypcp3/lcd-target.h new file mode 100644 index 0000000000..7879fa5010 --- /dev/null +++ b/firmware/target/arm/rk27xx/ypcp3/lcd-target.h @@ -0,0 +1,25 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2026 by Marcin Bukat + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#ifndef LCD_TARGET_H +#define LCD_TARGET_H + +#define LCD_DATABUS_WIDTH LCDIF_18BIT + +#endif diff --git a/firmware/target/arm/rk27xx/ypcp3/lcd-ypcp3.c b/firmware/target/arm/rk27xx/ypcp3/lcd-ypcp3.c new file mode 100644 index 0000000000..5b0adcc422 --- /dev/null +++ b/firmware/target/arm/rk27xx/ypcp3/lcd-ypcp3.c @@ -0,0 +1,173 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2014, 2026 by Marcin Bukat + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +/* Samsung YP-CP3 LCD: a 240x400 panel with an SPFD5420A-compatible + * controller on the rk27xx LCD interface's 18-bit MCU bus, used in landscape. + * + * The init sequence is the one worked out over hwstub + * (utils/hwstub/tools/lua/samsungypcp3.lua). It differs from the rk27xx + * generic panel's in the gamma curve and in leaving VCOM_HV1 alone. The + * interface side - pins, LCDC, DMA, updates - is lcdif-rk27xx.c. */ + +#include "config.h" +#include "kernel.h" +#include "lcd.h" +#include "system.h" +#include "cpu.h" +#include "spfd5420a.h" +#include "lcdif-rk27xx.h" + +static void lcd_sleep(bool sleep) +{ + if (sleep) + { + lcd_write_reg(DISPLAY_CTRL1, 0x0170); + udelay(50); + lcd_write_reg(DISPLAY_CTRL1, 0x0000); + udelay(50); + lcd_write_reg(PWR_CTRL1, 0x14B4); + } + else + { + lcd_write_reg(PWR_CTRL1, 0x14B0); + udelay(50); + lcd_write_reg(DISPLAY_CTRL1, 0x0173); + } + + lcd_cmd(GRAM_WRITE); +} + +void lcd_display_init(void) +{ + int i; + + lcd_write_reg(RESET, 0x0001); + udelay(100000); + lcd_write_reg(RESET, 0x0000); + udelay(100000); + + lcd_write_reg(IF_ENDIAN, 0x0000); + lcd_write_reg(DRIVER_OUT_CTRL, 0x0000); + lcd_write_reg(WAVEFORM_CTRL, 0x0100); + lcd_write_reg(ENTRY_MODE, 0x1038); /* landscape */ + lcd_write_reg(SHAPENING_CTRL, 0x0000); + lcd_write_reg(DISPLAY_CTRL2, 0x0808); + lcd_write_reg(LOW_PWR_CTRL1, 0x0001); + lcd_write_reg(LOW_PWR_CTRL2, 0x0010); + lcd_write_reg(EXT_DISP_CTRL1, 0x0000); + lcd_write_reg(EXT_DISP_CTRL2, 0x0000); + lcd_write_reg(BASE_IMG_SIZE, 0x3100); + lcd_write_reg(BASE_IMG_CTRL, 0x0001); + lcd_write_reg(VSCROLL_CTRL, 0x0000); + lcd_write_reg(PART1_POS, 0x0000); + lcd_write_reg(PART1_START, 0x0000); + lcd_write_reg(PART1_END, 0x018F); + lcd_write_reg(PART2_POS, 0x0000); + lcd_write_reg(PART2_START, 0x0000); + lcd_write_reg(PART2_END, 0x0000); + + lcd_write_reg(PANEL_IF_CTRL1, 0x0011); + udelay(100); + lcd_write_reg(PANEL_IF_CTRL2, 0x0202); + lcd_write_reg(PANEL_IF_CTRL3, 0x0300); + udelay(100); + lcd_write_reg(PANEL_IF_CTRL4, 0x021E); + lcd_write_reg(PANEL_IF_CTRL5, 0x0202); + lcd_write_reg(PANEL_IF_CTRL6, 0x0100); + lcd_write_reg(FRAME_MKR_CTRL, 0x0000); + lcd_write_reg(MDDI_CTRL, 0x0000); + + lcd_write_reg(GAMMA_CTRL1, 0x0101); + lcd_write_reg(GAMMA_CTRL2, 0x0024); + lcd_write_reg(GAMMA_CTRL3, 0x1321); + lcd_write_reg(GAMMA_CTRL4, 0x2613); + lcd_write_reg(GAMMA_CTRL5, 0x2400); + lcd_write_reg(GAMMA_CTRL6, 0x0100); + lcd_write_reg(GAMMA_CTRL7, 0x1704); + lcd_write_reg(GAMMA_CTRL8, 0x0417); + lcd_write_reg(GAMMA_CTRL9, 0x0007); + lcd_write_reg(GAMMA_CTRL10, 0x0101); + lcd_write_reg(GAMMA_CTRL11, 0x0F05); + lcd_write_reg(GAMMA_CTRL12, 0x0F01); + lcd_write_reg(GAMMA_CTRL13, 0x010F); + lcd_write_reg(GAMMA_CTRL14, 0x050F); + lcd_write_reg(GAMMA_CTRL15, 0x0501); + lcd_write_reg(GAMMA_CTRL16, 0x0700); + + /* power on */ + lcd_write_reg(DISPLAY_CTRL1, 0x0001); + lcd_write_reg(PWR_CTRL6, 0x0001); + lcd_write_reg(PWR_CTRL7, 0x0060); + udelay(500); + lcd_write_reg(PWR_CTRL1, 0x16B0); + udelay(100); + lcd_write_reg(PWR_CTRL2, 0x0147); + udelay(100); + lcd_write_reg(PWR_CTRL3, 0x0117); + udelay(100); + lcd_write_reg(PWR_CTRL4, 0x2F00); + udelay(500); + lcd_write_reg(VCOM_HV2, 0x0000); + udelay(100); + lcd_write_reg(PWR_CTRL3, 0x01BE); + udelay(100); + + /* the whole panel */ + lcd_write_reg(WINDOW_H_START, 0x0000); + lcd_write_reg(WINDOW_H_END, 0x00EF); /* 239 */ + lcd_write_reg(WINDOW_V_START, 0x0000); + lcd_write_reg(WINDOW_V_END, 0x018F); /* 399 */ + lcd_write_reg(GRAM_H_ADDR, 0x0000); + lcd_write_reg(GRAM_V_ADDR, 0x0000); + + /* display on */ + lcd_write_reg(DISPLAY_CTRL1, 0x0021); + udelay(400); + lcd_write_reg(DISPLAY_CTRL1, 0x0061); + udelay(1000); + lcd_write_reg(DISPLAY_CTRL1, 0x0173); + udelay(3000); + + /* clear */ + lcd_cmd(GRAM_WRITE); + for (i = 0; i < LCD_WIDTH * LCD_HEIGHT; i++) + { + lcd_data(0x000000); + } + + lcd_sleep(false); +} + +void lcd_set_gram_area(int x_start, int y_start, int x_end, int y_end) +{ + lcdctrl_bypass(1); + LCDC_CTRL |= RGB24B; + + /* the panel is portrait: its horizontal is our vertical */ + lcd_write_reg(WINDOW_H_START, y_start); + lcd_write_reg(WINDOW_H_END, y_end); + lcd_write_reg(WINDOW_V_START, x_start); + lcd_write_reg(WINDOW_V_END, x_end); + lcd_write_reg(GRAM_H_ADDR, y_start); + lcd_write_reg(GRAM_V_ADDR, x_start); + + lcd_cmd(GRAM_WRITE); + LCDC_CTRL &= ~RGB24B; +} diff --git a/firmware/target/arm/rk27xx/ypcp3/power-ypcp3.c b/firmware/target/arm/rk27xx/ypcp3/power-ypcp3.c new file mode 100644 index 0000000000..755d44c88a --- /dev/null +++ b/firmware/target/arm/rk27xx/ypcp3/power-ypcp3.c @@ -0,0 +1,70 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2026 by Marcin Bukat + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#include +#include "config.h" +#include "power.h" +#include "system.h" +#include "usb.h" + +/* Power is held on by GPIO C0, active high - as on the rk27xx generic + * board. The original firmware raises it at boot and, to switch off, + * drives it low and waits for the supply to go. */ +#define POWER_HOLD_PIN (1 << 0) /* GPIO C0 */ + +void power_init(void) +{ + GPIO_PCDR |= POWER_HOLD_PIN; + GPIO_PCCON |= POWER_HOLD_PIN; /* output */ +} + +void power_off(void) +{ + disable_irq(); + GPIO_PCCON |= POWER_HOLD_PIN; + GPIO_PCDR &= ~POWER_HOLD_PIN; + while (1) + { + } +} + +#if CONFIG_TUNER +/* The tuner has no power switch; the original firmware does not switch it + * either, it only puts the chip to sleep over I2C. */ +bool tuner_power(bool status) +{ + (void)status; + return true; +} + +bool tuner_powered(void) +{ + return true; +} +#endif + +unsigned int power_input_status(void) +{ + return (usb_detect() == USB_INSERTED) ? POWER_INPUT_MAIN_CHARGER : POWER_INPUT_NONE; +} + +bool charging_state(void) +{ + return usb_detect() == USB_INSERTED; +} diff --git a/firmware/target/arm/rk27xx/ypcp3/powermgmt-ypcp3.c b/firmware/target/arm/rk27xx/ypcp3/powermgmt-ypcp3.c new file mode 100644 index 0000000000..808fd3855a --- /dev/null +++ b/firmware/target/arm/rk27xx/ypcp3/powermgmt-ypcp3.c @@ -0,0 +1,61 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright © 2009 Bertrik Sikken + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#include "config.h" +#include "adc.h" +#include "adc-target.h" +#include "powermgmt.h" + +/* Battery voltage calculation and discharge/charge curves for the Samsung YP-CP3. + + TODO: copied from the rk27xx generic board - the YP-CP3's battery ADC + channel, divider and curves are not measured yet. + + Battery voltage is calculated under the assumption that the adc full-scale + readout represents 3.00V and that the battery ADC channel is fed with + exactly half of the battery voltage (through a resistive divider). + Discharge and charge curves have not been calibrated yet. +*/ + +unsigned short battery_level_disksafe = 3400; /* TODO: this is just an initial guess */ + +unsigned short battery_level_shutoff = 3300; /* TODO: this is just an initial guess */ + +/* voltages (millivolt) of 0%, 10%, ... 100% when charging disabled */ +unsigned short percent_to_volt_discharge[11] = +{ + /* TODO: simple uncalibrated curve, linear except for first 10% */ + 3300, 3600, 3665, 3730, 3795, 3860, 3925, 3990, 4055, 4120, 4185 +}; + +/* voltages (millivolt) of 0%, 10%, ... 100% when charging enabled */ +unsigned short percent_to_volt_charge[11] = + /* TODO: simple uncalibrated curve, linear except for first 10% */ + { 3300, 3600, 3665, 3730, 3795, 3860, 3925, 3990, 4055, 4120, 4185 }; + +/* full-scale ADC readout (2^10) in millivolt */ +#define BATTERY_SCALE_FACTOR 6000 + +/* Returns battery voltage from ADC [millivolts] */ +int _battery_voltage(void) +{ + return (adc_read(ADC_BATTERY) * BATTERY_SCALE_FACTOR) >> 10; +} + diff --git a/firmware/target/arm/rk27xx/ypcp3/wmcodec-ypcp3.c b/firmware/target/arm/rk27xx/ypcp3/wmcodec-ypcp3.c new file mode 100644 index 0000000000..16ba0bf0f2 --- /dev/null +++ b/firmware/target/arm/rk27xx/ypcp3/wmcodec-ypcp3.c @@ -0,0 +1,36 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2026 by Marcin Bukat + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#include "config.h" +#include "system.h" +#include "i2c-rk27xx.h" +#include "wmcodec.h" + +/* The WM8750's control interface in 2-wire mode, CSB low */ +#define WM8750_I2C_ADDR 0x34 + +/* A register write is two bytes: the 7-bit register number over bit 8 of + * the value, then the value's low byte. */ +void wmcodec_write(int reg, int data) +{ + unsigned char lo = data & 0xff; + + i2c_write(WM8750_I2C_ADDR, (reg << 1) | ((data >> 8) & 1), 1, &lo); +} diff --git a/firmware/target/arm/s3c2440/gigabeat-fx/pcm-meg-fx.c b/firmware/target/arm/s3c2440/gigabeat-fx/pcm-meg-fx.c index 432862d39d..c76790d609 100644 --- a/firmware/target/arm/s3c2440/gigabeat-fx/pcm-meg-fx.c +++ b/firmware/target/arm/s3c2440/gigabeat-fx/pcm-meg-fx.c @@ -220,6 +220,7 @@ struct pcm_sink builtin_pcm_sink = { .samprs = hw_freq_sampr, .num_samprs = HW_NUM_FREQ, .default_freq = HW_FREQ_DEFAULT, + .volume_type = PCM_NATIVE_VOLUME_TYPE, }, .ops = { .init = sink_dma_init, diff --git a/firmware/target/arm/s3c2440/system-s3c2440.c b/firmware/target/arm/s3c2440/system-s3c2440.c index 938ee0276a..060065e20d 100644 --- a/firmware/target/arm/s3c2440/system-s3c2440.c +++ b/firmware/target/arm/s3c2440/system-s3c2440.c @@ -195,12 +195,6 @@ void system_init(void) #endif } -int system_memory_guard(int newmode) -{ - (void)newmode; - return 0; -} - #ifdef HAVE_ADJUSTABLE_CPU_FREQ void set_cpu_frequency(long frequency) diff --git a/firmware/target/arm/s5l8700/pcm-s5l8700.c b/firmware/target/arm/s5l8700/pcm-s5l8700.c index 5bd2d4046d..8e36c9d60e 100644 --- a/firmware/target/arm/s5l8700/pcm-s5l8700.c +++ b/firmware/target/arm/s5l8700/pcm-s5l8700.c @@ -272,6 +272,7 @@ struct pcm_sink builtin_pcm_sink = { .samprs = hw_freq_sampr, .num_samprs = HW_NUM_FREQ, .default_freq = HW_FREQ_DEFAULT, + .volume_type = PCM_NATIVE_VOLUME_TYPE, }, .ops = { .init = sink_dma_init, diff --git a/firmware/target/arm/s5l8700/system-s5l8700.c b/firmware/target/arm/s5l8700/system-s5l8700.c index eb9dd09881..81d574bfef 100644 --- a/firmware/target/arm/s5l8700/system-s5l8700.c +++ b/firmware/target/arm/s5l8700/system-s5l8700.c @@ -207,12 +207,6 @@ void system_exception_wait(void) while(1); } -int system_memory_guard(int newmode) -{ - (void)newmode; - return 0; -} - #ifdef HAVE_ADJUSTABLE_CPU_FREQ void set_cpu_frequency(long frequency) diff --git a/firmware/target/arm/s5l8702/debug-s5l8702.c b/firmware/target/arm/s5l8702/debug-s5l8702.c index 9b58c46dfa..fbf780b20e 100644 --- a/firmware/target/arm/s5l8702/debug-s5l8702.c +++ b/firmware/target/arm/s5l8702/debug-s5l8702.c @@ -35,6 +35,9 @@ #include "uart-target.h" #include "uc87xx.h" #endif +#ifdef HAVE_MIKEY_REMOTE +#include "mikey-target.h" +#endif #include "clocking-s5l8702.h" #define DEBUG_CANCEL BUTTON_MENU @@ -133,6 +136,14 @@ bool dbg_hw_info(void) #ifdef IPOD_ACCESSORY_PROTOCOL _DEBUG_PRINTF("accessory present: %s", pmu_accessory_present() ? "true" : "false"); +#endif +#ifdef HAVE_MIKEY_REMOTE + /* r4/r5 are live register reads to help characterize the + * remote-ID behavior across units (it varies; see + * mikey-6g.c). r4 bit6 = ID bit, r5 = event register. */ + _DEBUG_PRINTF("mikey remote ctrl: %s r4=%02x r5=%02x", + mikey_present() ? "ok" : "--", + mikey_read(4), mikey_read(5)); #endif line++; _DEBUG_PRINTF("ADC:"); diff --git a/firmware/target/arm/s5l8702/ipod6g/audio-6g.c b/firmware/target/arm/s5l8702/ipod6g/audio-6g.c index 9775325cdc..244502f0e0 100644 --- a/firmware/target/arm/s5l8702/ipod6g/audio-6g.c +++ b/firmware/target/arm/s5l8702/ipod6g/audio-6g.c @@ -23,34 +23,10 @@ #include "sound.h" #include "pmu-target.h" #include "i2c-s5l8702.h" +#include "mikey-target.h" extern int rec_hw_ver; -/* Mikey is the internal controller for jack microphone and/or - * remote accessories. - * TODO: - * - move to mikey-6g.c - * - detect jack accessory - * - support for remote buttons - */ -unsigned char mikey_read(int address) -{ - unsigned char val; - i2c_read(0, 0x72, address, 1, &val); - return val; -} - -int mikey_write(int address, unsigned char val) -{ - return i2c_write(0, 0x72, address, 1, &val); -} - -void mikey_reset(void) -{ - mikey_write(0, 5); - mikey_write(1, 0x80); -} - #if INPUT_SRC_CAPS != 0 #ifdef HAVE_RECORDING void audio_enable_mic(bool enable) @@ -60,12 +36,12 @@ void audio_enable_mic(bool enable) if (enable) { - mikey_write(0, 7); /* raise voltage to polarize microphone */ + mikey_set_mic_capture(true); /* polarize mic, pause the remote */ GPIOCMD = 0xe070f; /* enable preamp */ } else { - mikey_reset(); /* microphone line voltage = 0 */ + mikey_set_mic_capture(false); GPIOCMD = 0xe070e; /* disable preamp */ } } diff --git a/firmware/target/arm/s5l8702/ipod6g/mikey-6g.c b/firmware/target/arm/s5l8702/ipod6g/mikey-6g.c new file mode 100644 index 0000000000..8165e51469 --- /dev/null +++ b/firmware/target/arm/s5l8702/ipod6g/mikey-6g.c @@ -0,0 +1,342 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2026 by Hemant Kumar + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +/* "Mikey" is the internal controller for the headphone jack microphone and + * the inline earphone remote (I2C bus 0, address 0x72). + * + * Register protocol (reverse engineered on-device, 2026-07): + * reg0 = 0x2f remote-reporting mode. The low 3 bits are the mic bias + * level (7, same as the recording path uses); 0x28 arms + * the button detection engine. The chip resets on jack + * removal (all registers read zero afterwards), so the + * mode is re-armed on insertion. + * reg4 & 0x40 identified-remote bit, set after the chip hears an + * Apple remote's ID chirp. UNIT-DEPENDENT: reliably + * latched on one 6G, never observed on two other + * 120GB units, so nothing is gated on it; it is only + * shown in the hardware debug screen. + * reg4 & 0x05 center (play/pause) held; a level signal. Can + * flicker while the mode is (re)armed, hence the + * "armed" guard below. + * reg5 volume edge events, each readable for ~100ms: + * 0x04 vol+ press, 0x08 vol+ release, + * 0x01 vol- press, 0x02 vol- release. + * 0x30 is the accessory-ID event (no button meaning). + * The remote itself signals volume as static DC loads on the mic line and + * identifies via a one-time ultrasonic chirp (the same scheme David Carne + * documented for the shuffle 3G remote); Mikey does that level and chirp + * detection in hardware and reports the results as above. */ + +#include "system.h" +#include "cpu.h" +#include "kernel.h" +#include "thread.h" +#include "button.h" +#include "i2c-s5l8702.h" +#include "mikey-target.h" + +#define MIKEY_ADDR 0x72 +#define MIKEY_REG_MODE 0 /* mic bias + detection-engine mode */ +#define MIKEY_REG_BTN 4 /* center-button level */ +#define MIKEY_REG_EVT 5 /* volume press/release edge events */ + +#define MIKEY_MODE_OFF 0x05 /* reset default, mic line unpowered */ +#define MIKEY_MODE_MIC 0x07 /* mic bias raised, for recording */ +#define MIKEY_MODE_REMOTE 0x2f /* mic bias + button detection */ + +#define MIKEY_ID_FLAG 0x40 /* identified-remote latch in reg4 */ +#define MIKEY_BTN_CENTER 0x05 +#define MIKEY_EVT_VOLUP_DN 0x04 +#define MIKEY_EVT_VOLUP_UP 0x08 +#define MIKEY_EVT_VOLDN_DN 0x01 +#define MIKEY_EVT_VOLDN_UP 0x02 + +/* A press+release pair landing in a single poll is stretched over this + * many polls, so the button driver's debounce (two identical consecutive + * reads at the 10ms button tick) still accepts the click. */ +#define MIKEY_CLICK_POLLS 2 + +/* Center-button handling: click-only. reg4's rising edge is always + * immediate, but after ~5s of button inactivity the chip's event engine + * naps and reports the release (reg4 fall, reg6 events) ~1.8s late, so + * the press duration is unreliable. Every press is therefore reported + * as a fixed-length click at the rise; there is no hold/long-press. A + * release must be seen for several consecutive polls before a new rise + * counts, so bounce or an I2C NAK can't double-fire a click. */ +#define MIKEY_CENTER_PULSE_POLLS 3 /* reported click length, 60ms */ +#define MIKEY_CENTER_OFF_POLLS 3 /* release debounce, 60ms */ + +unsigned char mikey_read(int address) +{ + /* default to "no press, no events" if the transfer fails: the chip + * NAKs while it resets itself around jack removal/insertion */ + unsigned char val = 0; + i2c_read(0, MIKEY_ADDR, address, 1, &val); + return val; +} + +int mikey_write(int address, unsigned char val) +{ + return i2c_write(0, MIKEY_ADDR, address, 1, &val); +} + +void mikey_reset(void) +{ + mikey_write(MIKEY_REG_MODE, MIKEY_MODE_OFF); + mikey_write(1, 0x80); +} + +static int mikey_btn = BUTTON_NONE; +static volatile bool mic_active = false; +static bool mikey_detected = false; +static long mikey_stack[DEFAULT_STACK_SIZE/2/sizeof(long)]; + +bool mikey_present(void) +{ + return mikey_detected; +} + +/* The recording path owns the chip while the jack mic is enabled; the + * polling thread backs off and re-arms the remote mode afterwards. */ +void mikey_set_mic_capture(bool enable) +{ + mic_active = enable; + if (enable) + mikey_write(MIKEY_REG_MODE, MIKEY_MODE_MIC); + else + mikey_reset(); +} + +/* Decode one volume button's press/release edge events into a held state. + * A real hold only produces the two edges, so between them *held carries + * the state; *click stretches a same-poll press+release (see above). */ +static void mikey_decode_vol(unsigned char evt, unsigned char dn, + unsigned char up, bool *held, int *click) +{ + if (*click > 0 && --*click == 0) + *held = false; + + if (evt & dn) + { + *held = true; + *click = (evt & up) ? MIKEY_CLICK_POLLS : 0; + } + else if ((evt & up) && *click == 0) + *held = false; +} + +/* Phantom-load failsafe: some passive cables present a DC load on the + * mic line that Mikey decodes as a volume button, producing a press + * edge the moment the detection engine arms and never a release (seen + * in the wild as volume ramping to zero on its own). A press edge this + * early cannot be a human, so it suppresses that button until a + * release edge proves a real remote is attached. This is the + * edge-event analog of the center button's "armed" guard, which + * already keeps mic-less TRS plugs (a permanent mic-line short) from + * reading as a stuck center button. */ +#define MIKEY_ARM_WINDOW_POLLS 10 /* 200ms after (re)arming */ + +/* Per-poll decode state, kept apart from the thread's power/mode + * bookkeeping so the register-to-button logic is a pure function of + * (state, reg4, reg5). */ +struct mikey_decode { + bool armed; /* need a released reading before first press */ + bool vol_up, vol_dn; + int up_click, dn_click; + int arm_window; /* polls left of phantom-press suppression window */ + bool up_suppressed, dn_suppressed; + bool center_down; /* debounced "press already reported" */ + int center_off; /* consecutive released polls */ + int center_pulse; /* click pulse countdown */ +}; + +static void mikey_decode_reset(struct mikey_decode *d) +{ + d->armed = false; + d->vol_up = d->vol_dn = false; + d->up_click = d->dn_click = 0; + d->arm_window = MIKEY_ARM_WINDOW_POLLS; + d->up_suppressed = d->dn_suppressed = false; + d->center_down = false; + d->center_off = MIKEY_CENTER_OFF_POLLS; + d->center_pulse = 0; +} + +/* Apply the phantom-load failsafe to one volume button's edge bits: + * a press edge (without a same-poll release) inside the arm window + * marks the button suppressed; suppressed press edges are stripped + * until a release edge lifts the suppression. */ +static unsigned char mikey_vol_gate(struct mikey_decode *d, unsigned char evt, + unsigned char dn, unsigned char up, + bool *suppressed) +{ + if (evt & up) + *suppressed = false; + else if ((evt & dn) && d->arm_window > 0) + *suppressed = true; + + if (*suppressed) + evt &= ~dn; + return evt; +} + +/* Decode one poll's register pair into a button mask. Volume is NOT + * gated on the reg4 identified-remote bit: that bit proved to be + * unit-dependent (reliably latched on one 6G, never seen on two other + * 120GB units where gating on it broke volume entirely), so it is + * only surfaced in the hardware debug screen for now. */ +static int mikey_decode_poll(struct mikey_decode *d, + unsigned char btn, unsigned char evt) +{ + evt = mikey_vol_gate(d, evt, MIKEY_EVT_VOLUP_DN, MIKEY_EVT_VOLUP_UP, + &d->up_suppressed); + evt = mikey_vol_gate(d, evt, MIKEY_EVT_VOLDN_DN, MIKEY_EVT_VOLDN_UP, + &d->dn_suppressed); + if (d->arm_window > 0) + d->arm_window--; + + mikey_decode_vol(evt, MIKEY_EVT_VOLUP_DN, MIKEY_EVT_VOLUP_UP, + &d->vol_up, &d->up_click); + mikey_decode_vol(evt, MIKEY_EVT_VOLDN_DN, MIKEY_EVT_VOLDN_UP, + &d->vol_dn, &d->dn_click); + + /* reg4 can read pressed while the mode settles; don't report the + * center button until it has been seen released once */ + bool raw = (btn & MIKEY_BTN_CENTER) != 0; + if (!raw) + d->armed = true; + + /* click-only: emit a fixed pulse at each debounced rise */ + if (raw && d->armed) + { + if (!d->center_down && d->center_off >= MIKEY_CENTER_OFF_POLLS) + d->center_pulse = MIKEY_CENTER_PULSE_POLLS; + d->center_down = true; + d->center_off = 0; + } + else + { + if (d->center_off < MIKEY_CENTER_OFF_POLLS) + d->center_off++; + if (d->center_off >= MIKEY_CENTER_OFF_POLLS) + d->center_down = false; + } + + bool center = false; + if (d->center_pulse > 0) + { + center = true; + d->center_pulse--; + } + + /* All buttons report as multimedia keys: handled globally by + * default_event_handler on every screen, so volume and play/pause + * work in menus, WPS and plugins alike, matching the OF. */ + return (center ? BUTTON_MULTIMEDIA_PLAYPAUSE : 0) + | (d->vol_up ? BUTTON_MULTIMEDIA_VOLUME_UP : 0) + | (d->vol_dn ? BUTTON_MULTIMEDIA_VOLUME_DOWN : 0); +} + +/* The chip only ACKs the bus while something occupies the jack, so + * presence can't be probed at boot; it is probed here whenever the + * jack is occupied. A few insertion transients may NAK before the + * first ACK; a model without the chip (2007) NAKs forever, so after + * the retry budget the poll backs way off to stay out of the way. */ +#define MIKEY_PROBE_TRIES 25 /* ~2.5s of HZ/10 retries */ + +static void mikey_thread(void) +{ + bool powered = false; + int naks = 0; + struct mikey_decode decode; + + mikey_decode_reset(&decode); + + while (1) + { + if (!headphones_inserted() || mic_active) + { + if (powered && !mic_active) + mikey_reset(); + powered = false; + naks = 0; + mikey_decode_reset(&decode); + mikey_btn = BUTTON_NONE; + sleep(HZ/2); /* nothing to poll, check back at leisure */ + continue; + } + + /* Re-arm when the mode readback disagrees (first pass, chip reset + * on jack removal, or the recording path rewrote it). Arming can + * flicker reg4 and emits a spurious accessory-ID event, so drop + * the state and settle for one poll. */ + unsigned char mode; + if (i2c_read(0, MIKEY_ADDR, MIKEY_REG_MODE, 1, &mode) != 0) + { + /* no ACK: chip resetting around insertion, or not present */ + mikey_decode_reset(&decode); + mikey_btn = BUTTON_NONE; + if (naks < MIKEY_PROBE_TRIES) + { + naks++; + sleep(HZ/10); + } + else + sleep(HZ*2); + continue; + } + naks = 0; + powered = true; + mikey_detected = true; /* latched for the hw debug screen */ + + if (mode != MIKEY_MODE_REMOTE) + { + mikey_write(MIKEY_REG_MODE, MIKEY_MODE_REMOTE); + mikey_decode_reset(&decode); + mikey_btn = BUTTON_NONE; + sleep(HZ/50); + continue; + } + + unsigned char evt = mikey_read(MIKEY_REG_EVT); + unsigned char btn = mikey_read(MIKEY_REG_BTN); + mikey_btn = mikey_decode_poll(&decode, btn, evt); + + /* volume edge events stay readable for ~100ms; 20ms leaves a + * comfortable margin against scheduling jitter */ + sleep(HZ/50); + } +} + +int mikey_button_read(void) +{ + return mikey_btn; +} + +void mikey_init(void) +{ + /* Presence can't be probed here: the chip only ACKs while the + * jack is occupied (booting without headphones must not disable + * the remote). The thread probes on insertion instead and backs + * off on models without the chip (2007). */ + mikey_reset(); + create_thread(mikey_thread, mikey_stack, sizeof(mikey_stack), 0, + "mikey" IF_PRIO(, PRIORITY_SYSTEM) IF_COP(, CPU)); +} diff --git a/firmware/target/arm/s5l8702/ipod6g/mikey-target.h b/firmware/target/arm/s5l8702/ipod6g/mikey-target.h new file mode 100644 index 0000000000..888c14cde6 --- /dev/null +++ b/firmware/target/arm/s5l8702/ipod6g/mikey-target.h @@ -0,0 +1,49 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2026 by Hemant Kumar + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#ifndef __MIKEY_TARGET_H__ +#define __MIKEY_TARGET_H__ + +/* "Mikey" is the internal controller for the headphone jack microphone + * and inline remote (I2C bus 0, address 0x72). */ + +/* Low-level register access. */ +unsigned char mikey_read(int address); +int mikey_write(int address, unsigned char val); +void mikey_reset(void); + +/* Recording-path handover: while the jack mic is enabled the remote + * polling backs off and Mikey stays in plain mic-bias mode. */ +void mikey_set_mic_capture(bool enable); + +/* Inline-remote button support. mikey_init() starts a polling thread; + * mikey_button_read() returns the current button mask (multimedia key + * codes, handled globally by default_event_handler: PLAYPAUSE for the + * center click, VOLUME_UP/DOWN for volume; BUTTON_NONE if nothing + * pressed) and is safe to call from the button tick. */ +void mikey_init(void); +int mikey_button_read(void); + +/* True once the controller has ever ACKed the bus (it only responds + * while the jack is occupied, so this latches on first insertion; + * stays false on the early 2007 models, which lack the chip). Shown + * in the hardware debug screen. */ +bool mikey_present(void); + +#endif /* __MIKEY_TARGET_H__ */ diff --git a/firmware/target/arm/s5l8702/ipod6g/power-6g.c b/firmware/target/arm/s5l8702/ipod6g/power-6g.c index c1ff86092f..8f4c245c23 100644 --- a/firmware/target/arm/s5l8702/ipod6g/power-6g.c +++ b/firmware/target/arm/s5l8702/ipod6g/power-6g.c @@ -25,11 +25,18 @@ #include "panic.h" #include "pmu-target.h" #include "usb_core.h" /* for usb_charging_maxcurrent_change */ +#ifndef BOOTLOADER +#include "videoout.h" +#endif static int idepowered; void power_off(void) { +#ifndef BOOTLOADER + videoout_disable(); +#endif + /* USB inserted or EXTON1 */ pmu_set_wake_condition( PCF5063X_OOCWAKE_EXTON2 | PCF5063X_OOCWAKE_EXTON1); diff --git a/firmware/target/arm/s5l8702/ipod6g/serial-6g.c b/firmware/target/arm/s5l8702/ipod6g/serial-6g.c index c9e8247144..d1c1cf87c7 100644 --- a/firmware/target/arm/s5l8702/ipod6g/serial-6g.c +++ b/firmware/target/arm/s5l8702/ipod6g/serial-6g.c @@ -106,6 +106,9 @@ void serial_setup(void) #include "kernel.h" #include "pmu-target.h" #include "iap.h" +#ifndef BOOTLOADER +#include "videoout.h" +#endif static enum { ABR_STATUS_LAUNCHED, /* ST_SYNC */ @@ -115,13 +118,58 @@ static enum { static int bitrate = 0; static bool acc_plugged = false; +static unsigned int acc_absent_ticks; +#ifndef BOOTLOADER +static long videoout_identify_tick; +static bool videoout_identified; +static enum videoout_accessory videoout_accessory = + VIDEOOUT_ACCESSORY_NONE; + +/* The Philips is a silent legacy dock. Do not request identification during + * the connector and iAP startup window: the physically qualified downstream + * build gives silent docks ten seconds before enabling video. */ +#define VIDEOOUT_IDENTIFY_TICKS (10 * HZ) + +static void serial_videoout_publish(enum videoout_accessory accessory) +{ + if (videoout_accessory != accessory) + { + videoout_accessory = accessory; + videoout_accessory_state(accessory); + } +} +#endif + +/* A seated 30-pin accessory can briefly lose its presence contact without + * leaving the connector. Requiring 250 ms of continuous absence avoids + * tearing down and immediately reopening the serial/video session on contact + * bounce while adding only a bounded delay to a real unplug. */ +#define ACCESSORY_REMOVE_DEBOUNCE_TICKS MAX(1, (HZ + 3) / 4) static void serial_acc_tick(void) { bool plugged = pmu_accessory_present(); + + if (acc_plugged && !plugged) + { + if (++acc_absent_ticks < ACCESSORY_REMOVE_DEBOUNCE_TICKS) + return; + } + else + { + acc_absent_ticks = 0; + } + if (acc_plugged != plugged) { acc_plugged = plugged; +#ifndef BOOTLOADER + videoout_identify_tick = current_tick; + videoout_identified = false; + serial_videoout_publish(acc_plugged ? + VIDEOOUT_ACCESSORY_PENDING : + VIDEOOUT_ACCESSORY_NONE); +#endif if (acc_plugged) { uartc_open(ser_port.uartc); @@ -139,6 +187,19 @@ static void serial_acc_tick(void) uartc_close(ser_port.uartc); } } + +#ifndef BOOTLOADER + if (acc_plugged && !videoout_identified && + !TIME_BEFORE(current_tick, + videoout_identify_tick + VIDEOOUT_IDENTIFY_TICKS)) + { + videoout_identified = true; + /* A tick task runs in the timer interrupt. The actual ADC read takes + * a mutex and sleeps for its bias-settle interval, so it must be + * deferred to the normal LCD/settings context. */ + videoout_request_accessory_identification(); + } +#endif } void serial_setup(void) diff --git a/firmware/target/arm/s5l8702/ipod6g/videoout-6g.c b/firmware/target/arm/s5l8702/ipod6g/videoout-6g.c new file mode 100644 index 0000000000..6c45cedc8e --- /dev/null +++ b/firmware/target/arm/s5l8702/ipod6g/videoout-6g.c @@ -0,0 +1,923 @@ +/*************************************************************************** + * iPod Classic 6G/7G S5L8702 composite video output + * + * Copyright (C) 2026 David Cormier + * + * The register sequence and private planar format were reconstructed from + * Apple's iPod Classic firmware and physically qualified on a Philips + * DCP750/37. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + ****************************************************************************/ + +#include "config.h" +#include "system.h" +#include "kernel.h" +#include "s5l87xx.h" +#include "clocking-s5l8702.h" +#include "lcd-s5l8702.h" +#include "adc-target.h" +#include "pmu-target.h" +#include "videoout.h" +#include + +#include + +/* RetailOS 35.2.0.4 format 8 selects the S5L8702 private planar layout. + * In private plane mode 1, offsets 0x28, 0x2c, and 0x30 carry the three + * YUV420 planes rather than the later public Samsung top/bottom-field + * layout. */ +/* The S5L8702 mixer predates the public S5P register layout. RetailOS + * 35.2.0.4 writes layer-0 base, position, and dimensions at 0x10, 0x14, and + * 0x18. Offset 0x14 is not a source stride. */ +/* Qualified on the DCP750: a centered 90% NTSC viewport keeps every + * Rockbox UI pixel inside the panel's composite overscan area. */ +#define SVID_UI_DESTINATION_X 36u +#define SVID_UI_DESTINATION_Y 24u +#define SVID_UI_DESTINATION_WIDTH 648u +#define SVID_UI_DESTINATION_HEIGHT 432u +/* RetailOS's nominal 4:3 movie path supplies private format 8 with a native + * 320x240 planar source. The VP, not software, scales that source into the + * 720x480 NTSC destination. This exact source geometry matters: the DCP750 + * repeatedly rejected the otherwise equivalent 640x240 software-expanded + * layout at logical source row 120 regardless of image/source height values. + * One Cb and one Cr sample cover a 2x2 block of native luma samples. */ +#define SVID_PLANAR_Y_WIDTH LCD_WIDTH +#define SVID_PLANAR_Y_HEIGHT LCD_HEIGHT +#define SVID_PLANAR_SOURCE_HEIGHT SVID_PLANAR_Y_HEIGHT +#define SVID_PLANAR_Y_SIZE (SVID_PLANAR_Y_WIDTH * \ + SVID_PLANAR_Y_HEIGHT) +#define SVID_PLANAR_C_WIDTH (SVID_PLANAR_Y_WIDTH / 2) +#define SVID_PLANAR_C_HEIGHT (SVID_PLANAR_Y_HEIGHT / 2) +#define SVID_PLANAR_C_SIZE (SVID_PLANAR_C_WIDTH * \ + SVID_PLANAR_C_HEIGHT) +#define SVID_PLANAR_FRAME_SIZE (SVID_PLANAR_Y_SIZE + \ + 2 * SVID_PLANAR_C_SIZE) + +#define SVID_CLOCK_SOURCE 3 +#define SVID_CLOCK_DIVIDER 4 +#define SVID_CLOCK_POLL_MAX 1000 +#define SVID_POWER_GATE_0 14 +#define SVID_POWER_GATE_1 15 +#define SVID_POWER_GATE_2 16 +#define SVID_POWER_GATE_MASK ((1u << SVID_POWER_GATE_0) | \ + (1u << SVID_POWER_GATE_1) | \ + (1u << SVID_POWER_GATE_2)) + +#define SVID_GPIO_E4_VIDEO 0x000a040fu +#define SVID_GPIO_E4_MASK (0xfu << 16) + +struct svid_regval +{ + uint16_t offset; + uint32_t value; +}; + +static bool svid_active; +static bool svid_layer_active; +static bool svid_layer_planar; +static bool svid_mirror_enabled; +static bool svid_bus_boosted; +/* 96 bytes total. These target-owned tables replace three software + * divisions per mirrored pixel without taking core or playback memory. */ +static uint8_t svid_rgb5_to_8[32]; +static uint8_t svid_rgb6_to_8[64]; +static bool svid_rgb_expansion_ready; +static uint8_t svid_output_framebuffer[SVID_PLANAR_FRAME_SIZE] + CACHEALIGN_ATTR; +static const void *svid_policy_framebuffer; +static int svid_policy_width; +static int svid_policy_height; +static volatile enum videoout_mode svid_mode = VIDEOOUT_OFF; +static volatile enum videoout_accessory svid_accessory = + VIDEOOUT_ACCESSORY_NONE; +static volatile bool svid_policy_pending; +static volatile bool svid_identification_pending; +static bool svid_platform_saved; +static uint16_t svid_saved_clock; +static uint32_t svid_saved_power_gates; +static uint32_t svid_saved_gpio_e4; + +static void svid_apply_policy(void); + +/* Auto remains deliberately narrow: this is the physically observed + * DCP750/37 accessory-identify resistor range. */ +#define VIDEOOUT_DCP750_RID_MIN 500000 +#define VIDEOOUT_DCP750_RID_MAX 575000 + +static void svid_identify_accessory(void) +{ + int resistor; + + if (!svid_identification_pending) + return; + + svid_identification_pending = false; + if (!pmu_accessory_present()) + { + svid_accessory = VIDEOOUT_ACCESSORY_NONE; + return; + } + + /* This function is reached only from normal LCD/settings context. The + * ADC implementation takes a mutex, sleeps for its 50 ms bias-settle + * interval, and yields while converting; none of that is IRQ-safe. */ + resistor = adc_read_accessory_resistor(); + + /* The connector may have been removed while the normal thread slept. */ + if (!pmu_accessory_present()) + svid_accessory = VIDEOOUT_ACCESSORY_NONE; + else if (resistor >= VIDEOOUT_DCP750_RID_MIN && + resistor <= VIDEOOUT_DCP750_RID_MAX) + svid_accessory = VIDEOOUT_ACCESSORY_VIDEO; + else + svid_accessory = VIDEOOUT_ACCESSORY_OTHER; +} + +static void svid_set_bus_boost(bool enable) +{ + if (enable == svid_bus_boosted) + return; + + /* The VP memory reader underruns when Rockbox lowers HClk to 54 MHz. + * Own one normal boost reference for the lifetime of every memory-backed + * layer; this holds HClk at 108 MHz without interfering with other boost + * owners, and the paired release restores normal power policy. */ + cpu_boost(enable); + svid_bus_boosted = enable; +} + +static const struct svid_regval compositor_init[] = { + {0x004, 0}, {0x008, 0}, {0x00c, 0}, + {0x028, 0x08000000}, {0x02c, 0x08000000}, + {0x030, 0x08000000}, {0x034, 0x08000000}, + {0x038, 0x08000000}, + {0x03c, 64}, {0x040, 16}, {0x044, 0}, {0x048, 0}, + {0x04c, 64}, {0x050, 16}, {0x054, 0}, {0x058, 0}, + {0x05c, 64}, {0x060, 16}, {0x064, 512}, {0x068, 512}, + {0x3c0, 1}, {0x3cc, 1}, + {0x06c, 0x00070707}, {0x070, 0x07070707}, + {0x074, 0x07070707}, {0x078, 0x07000000}, + {0x07c, 0x00020405}, {0x080, 0x06060606}, + {0x084, 0x06050504}, {0x088, 0x03020101}, + {0x08c, 0x007a7470}, {0x090, 0x6e6c6b6c}, + {0x094, 0x6c6e7073}, {0x098, 0x76787b7e}, + {0x09c, 0x7f7e7d79}, {0x0a0, 0x726b6359}, + {0x0a4, 0x4f44392e}, {0x0a8, 0x23191008}, + {0x0ec, 0x003d3a38}, {0x0f0, 0x38383839}, + {0x0f4, 0x3a3b3c3d}, {0x0f8, 0x3e3f3f00}, + {0x0fc, 0x7f7e7c76}, {0x100, 0x6f665c51}, + {0x104, 0x463b3025}, {0x108, 0x1b130b05}, + {0x10c, 0x00050b13}, {0x110, 0x1b25303b}, + {0x114, 0x46515c66}, {0x118, 0x6f767c7e}, + {0x11c, 0x00003f3f}, {0x120, 0x3e3d3c3b}, + {0x124, 0x3a393838}, {0x128, 0x38383a3d}, + {0x12c, 0x6b6b6d6f}, {0x130, 0x3336393d}, + {0x134, 0x3f010203}, {0x138, 0x03030202}, + {0x13c, 0xaaa69f95}, {0x140, 0x88786754}, + {0x144, 0x412e1e0f}, {0x148, 0x0279726d}, + {0x200, 1}, {0x20c, 0}, {0x210, 0}, + {0x218, 0x80}, {0x21c, 0x80000080}, + {0x220, 0x80}, {0x224, 0x80}, {0x228, 0x80}, + {0x22c, 0x80}, {0x230, 0x80}, {0x234, 0x80}, + {0x238, 0}, +}; + +static const struct svid_regval router_init[] = { + {SVID_MXR_STATUS, SVID_MXR_STATUS_IDLE | SVID_MXR_STATUS_SYNC}, + {SVID_MXR_CONFIG, 0}, + {SVID_MXR_VIDEO_CONFIG, 0}, + {SVID_MXR_GRAPHIC0_CONFIG, 0}, + {SVID_MXR_GRAPHIC0_BASE, 0}, {SVID_MXR_GRAPHIC0_POS, 0}, + {SVID_MXR_GRAPHIC0_SIZE, 0}, + {0x01c, 0}, {0x020, 0}, {0x024, 0}, {0x028, 0}, {0x02c, 0}, + {0x030, 0}, {0x034, 0}, {0x038, 0}, {0x03c, 0}, + {SVID_MXR_GRAPHIC_FORMATS, 0}, {0x044, 0}, + {SVID_MXR_BG_COLOR0, 0x00108080}, + {SVID_MXR_BG_COLOR1, 0}, {SVID_MXR_BG_COLOR2, 0}, + {0x054, 0}, {0x058, 0}, + {0x080, 0x08440832}, {0x084, 0x3b4dace1}, + {0x088, 0x0e1d13dc}, {SVID_MXR_COMMIT, 1}, +}; + +static const struct svid_regval encoder_ntsc[] = { + {0x00c, 6}, {0x010, 1}, {0x014, 0x0000440c}, + {0x01c, 0x800}, {0x020, 0x800}, {0x024, 0x800}, + {0x028, 0x800}, {0x02c, 0x800}, {0x030, 0x800}, + {0x038, 0}, {0x03c, 0x01000700}, + {0x044, 0}, {0x048, 0}, {0x04c, 0}, {0x050, 0}, + {0x054, 0}, {0x058, 0}, {0x05c, 0}, {0x060, 0}, + {0x064, 0}, {0x068, 0}, {0x06c, 0}, + {0x070, 0x0000025d}, + {0x080, 0}, {0x084, 0}, {0x088, 0}, {0x08c, 0}, + {0x090, 0}, {0x094, 1}, {0x098, 7}, {0x09c, 20}, + {0x0a0, 40}, {0x0a4, 63}, {0x0a8, 82}, {0x0ac, 90}, + {0x0c0, 0}, {0x0c4, 0}, {0x0c8, 0}, {0x0cc, 0}, + {0x0d0, 0}, {0x0d4, 1}, {0x0d8, 9}, {0x0dc, 28}, + {0x0e0, 57}, {0x0e4, 90}, {0x0e8, 116}, {0x0ec, 126}, + {0x0f0, 0}, + {0x100, 0}, {0x104, 0}, {0x108, 0}, {0x10c, 0}, + {0x110, 0}, {0x114, 0}, {0x118, 0}, {0x11c, 0}, + {0x120, 0}, {0x124, 0}, {0x128, 0}, {0x12c, 0}, + {0x130, 0}, {0x134, 0}, {0x138, 0}, {0x13c, 0}, + {0x140, 0}, {0x144, 0}, {0x148, 0}, {0x14c, 0}, + {0x150, 0}, {0x154, 0}, {0x158, 0}, {0x15c, 0}, + {0x184, 0x00800000}, {0x188, 0x00800000}, + {0x18c, 0x80}, {0x190, 0}, {0x194, 0x0000eb10}, + {0x198, 0x02000000}, {0x19c, 0x03ff0200}, + {0x1a0, 0x1ff}, {0x1a4, 0x03ff0000}, + {0x1a8, 0x1ff}, {0x1c0, 17}, + {0x200, 0x00fd00fe}, {0x204, 0}, {0x208, 0x00050004}, + {0x20c, 0xff}, {0x210, 0x00f700fa}, {0x214, 1}, + {0x218, 0x000e000a}, {0x21c, 0x1ff}, + {0x220, 0x01ec01f2}, {0x224, 1}, + {0x228, 0x001d0014}, {0x22c, 0x000001fe}, + {0x230, 0x03d803e4}, {0x234, 2}, {0x238, 0x00380028}, + {0x23c, 0x3fd}, {0x240, 0x03b003c7}, {0x244, 5}, + {0x248, 0x00790056}, {0x24c, 0x000003f6}, + {0x250, 0x072c0766}, {0x254, 27}, + {0x258, 0x028b0265}, {0x25c, 0x04000ecc}, + {0x260, 0}, {0x264, 0}, {0x268, 0}, {0x26c, 0x00011a00}, + {0x280, 0}, {0x3c0, 0}, {0x3c4, 0x00010000}, + {0x3c8, 8}, {0x3cc, 0x00010000}, + {0x3d0, 1}, {0x3d4, 8}, +}; + +static void svid_write_table(uintptr_t base, + const struct svid_regval *table, + unsigned count) +{ + for (unsigned i = 0; i < count; i++) + SVID_REG(base, table[i].offset) = table[i].value; +} + +static bool svid_write_clock(uint16_t value) +{ + volatile uint32_t *reg32 = (volatile uint32_t *) + ((uintptr_t)&CG16_SVID & ~(uintptr_t)3); + unsigned shift = ((uintptr_t)&CG16_SVID & 2u) << 3; + uint32_t preserve = 0xffff0000u >> shift; + + *reg32 = (*reg32 & preserve) | ((uint32_t)value << shift); + for (unsigned i = 0; i < SVID_CLOCK_POLL_MAX; i++) + { + if (CG16_SVID == value) + return true; + udelay(1); + } + return false; +} + +static void svid_platform_restore(void) +{ + if (!svid_platform_saved) + return; + + PCON(14) = (PCON(14) & ~SVID_GPIO_E4_MASK) | svid_saved_gpio_e4; + PWRCON(0) = (PWRCON(0) & ~SVID_POWER_GATE_MASK) | + svid_saved_power_gates; + svid_write_clock(svid_saved_clock); + svid_platform_saved = false; +} + +static bool svid_platform_enable(void) +{ + uint16_t clock_value = + ((SVID_CLOCK_SOURCE & CG16_SEL_MSK) << CG16_SEL_POS) | + (((SVID_CLOCK_DIVIDER - 1) & CG16_DIV1_MSK) << CG16_DIV1_POS); + + svid_saved_clock = CG16_SVID; + svid_saved_power_gates = PWRCON(0) & SVID_POWER_GATE_MASK; + svid_saved_gpio_e4 = PCON(14) & SVID_GPIO_E4_MASK; + svid_platform_saved = true; + + /* Stock firmware selects PLL2 with a divide-by-four SVID clock. */ + if (!svid_write_clock(clock_value)) + { + svid_platform_restore(); + return false; + } + + clockgate_enable(SVID_POWER_GATE_0, true); + clockgate_enable(SVID_POWER_GATE_1, true); + clockgate_enable(SVID_POWER_GATE_2, true); + lcd_videoout_clock_acquire(); + GPIOCMD = SVID_GPIO_E4_VIDEO; + return true; +} + +static void svid_begin_reset(void) +{ + SVID_REG(SVID_COMPOSITOR_BASE, 0) &= 2; + svid_write_table(SVID_COMPOSITOR_BASE, compositor_init, + ARRAYLEN(compositor_init)); + + /* RetailOS initializes compositor, encoder, then output mixer. */ + SVID_REG(SVID_ENCODER_BASE, SVID_SDO_CLOCK) = + SVID_SDO_SOFTWARE_RESET; +} + +static void svid_finish_reset(void) +{ + SVID_REG(SVID_ENCODER_BASE, SVID_SDO_CLOCK) = 0; + SVID_REG(SVID_ENCODER_BASE, 0x180) &= ~0x1fu; + SVID_REG(SVID_ENCODER_BASE, 0x180) |= 0x10; + svid_write_table(SVID_ENCODER_BASE, encoder_ntsc, + ARRAYLEN(encoder_ntsc)); + svid_write_table(SVID_ROUTER_BASE, router_init, ARRAYLEN(router_init)); +} + +static void svid_select_composite(void) +{ + uint32_t timing = 0x792; + uint32_t reg = SVID_REG(SVID_ENCODER_BASE, SVID_SDO_CONFIG); + + /* RetailOS 35.2.0.4 does these as two adjacent operations in its output + * setup: clear mixer bit 2 for NTSC, then set mixer bit 1 before loading + * the interlaced SDO timing table. Every qualified failing build reached + * the mixer as C=0x10 instead of stock C=0x12 and presented only the + * first 120-line field before repeating its final row. */ + SVID_REG(SVID_ROUTER_BASE, SVID_MXR_CONFIG) = + (SVID_REG(SVID_ROUTER_BASE, SVID_MXR_CONFIG) & ~4u) | + SVID_MXR_SD_SCAN; + SVID_REG(SVID_ENCODER_BASE, 0x34) = 0; + /* RetailOS selects the dock's composite path with DAC mux values 1, 0, + * and 2. Clearing these bits produced sync with a green no-input field. */ + reg &= ~(SVID_SDO_DAC_MUX_MASK | SVID_SDO_COMPONENT | + SVID_SDO_PROGRESSIVE | SVID_SDO_STANDARD_MASK); + SVID_REG(SVID_ENCODER_BASE, SVID_SDO_CONFIG) = reg | 0x1200u; + + if (SVID_REG(0x3f000000u, 4) & 0x100u) + { + SVID_REG(SVID_ENCODER_BASE, 0x28) = timing + 0x23; + SVID_REG(SVID_ENCODER_BASE, 0x2c) = timing + 0x6d; + SVID_REG(SVID_ENCODER_BASE, 0x30) = timing; + } + else + { + SVID_REG(SVID_ENCODER_BASE, 0x28) = timing | (timing >> 7); + SVID_REG(SVID_ENCODER_BASE, 0x2c) = timing + 0x27; + SVID_REG(SVID_ENCODER_BASE, 0x30) = timing + 8; + } + +} + +static void svid_start_pipeline(void) +{ + SVID_REG(SVID_ENCODER_BASE, SVID_SDO_DAC) |= SVID_SDO_DAC_ALL_ON; + SVID_REG(SVID_ENCODER_BASE, SVID_SDO_CLOCK) |= SVID_SDO_CLOCK_ON; + SVID_REG(SVID_ROUTER_BASE, SVID_MXR_STATUS) |= SVID_MXR_STATUS_RUN; +} + +static void svid_mixer_commit(void) +{ + SVID_REG(SVID_ROUTER_BASE, SVID_MXR_COMMIT) = 1; +} + +static void svid_init_rgb_expansion(void) +{ + if (svid_rgb_expansion_ready) + return; + + for (unsigned value = 0; value < ARRAYLEN(svid_rgb5_to_8); value++) + svid_rgb5_to_8[value] = value * 255 / 31; + + for (unsigned value = 0; value < ARRAYLEN(svid_rgb6_to_8); value++) + svid_rgb6_to_8[value] = value * 255 / 63; + + svid_rgb_expansion_ready = true; +} + +static uint32_t svid_rgb565_to_ycbcr(uint16_t pixel) +{ + int red = svid_rgb5_to_8[(pixel >> 11) & 0x1f]; + int green = svid_rgb6_to_8[(pixel >> 5) & 0x3f]; + int blue = svid_rgb5_to_8[pixel & 0x1f]; + int y; + int cb; + int cr; + + /* ITU-R BT.601 limited-range values, matching the SD mixer. */ + /* The exhaustive color gate proves these limited-range expressions stay + * inside byte range for all 65,536 RGB565 inputs. */ + y = ((66 * red + 129 * green + 25 * blue + 128) >> 8) + 16; + cb = ((-38 * red - 74 * green + 112 * blue + 128) >> 8) + 128; + cr = ((112 * red - 94 * green - 18 * blue + 128) >> 8) + 128; + return (uint32_t)y | ((uint32_t)cb << 8) | ((uint32_t)cr << 16); +} + +static void svid_copy_framebuffer_planar(const uint16_t *source, + int width, int height) +{ + uint8_t *luma = (uint8_t *)svid_output_framebuffer; + uint8_t *cb = luma + SVID_PLANAR_Y_SIZE; + uint8_t *cr = cb + SVID_PLANAR_C_SIZE; + + memset(luma, 16, SVID_PLANAR_Y_SIZE); + memset(cb, 128, SVID_PLANAR_C_SIZE); + memset(cr, 128, SVID_PLANAR_C_SIZE); + + for (int y = 0; y < height; y += 2) + { + uint8_t *luma0 = luma + y * SVID_PLANAR_Y_WIDTH; + uint8_t *cb0 = cb + (y / 2) * SVID_PLANAR_C_WIDTH; + uint8_t *cr0 = cr + (y / 2) * SVID_PLANAR_C_WIDTH; + int y1 = MIN(y + 1, height - 1); + uint8_t *luma1 = luma + y1 * SVID_PLANAR_Y_WIDTH; + const uint16_t *source0 = source + y * width; + const uint16_t *source1 = source + y1 * width; + + for (int x = 0; x < width; x += 2) + { + int x1 = MIN(x + 1, width - 1); + uint32_t pixel00 = svid_rgb565_to_ycbcr(source0[x]); + uint32_t pixel01 = svid_rgb565_to_ycbcr(source0[x1]); + uint32_t pixel10 = svid_rgb565_to_ycbcr(source1[x]); + uint32_t pixel11 = svid_rgb565_to_ycbcr(source1[x1]); + + luma0[x] = pixel00; + luma0[x1] = pixel01; + luma1[x] = pixel10; + luma1[x1] = pixel11; + cb0[x / 2] = (((pixel00 >> 8) & 0xff) + + ((pixel01 >> 8) & 0xff) + + ((pixel10 >> 8) & 0xff) + + ((pixel11 >> 8) & 0xff) + 2) >> 2; + cr0[x / 2] = (((pixel00 >> 16) & 0xff) + + ((pixel01 >> 16) & 0xff) + + ((pixel10 >> 16) & 0xff) + + ((pixel11 >> 16) & 0xff) + 2) >> 2; + } + } + + commit_dcache_range(luma, SVID_PLANAR_FRAME_SIZE); +} + +static bool svid_enable_sync(void) +{ + svid_init_rgb_expansion(); + + if (svid_active) + return true; + + int oldlevel = disable_irq_save(); + if (!svid_platform_enable()) + { + restore_irq(oldlevel); + return false; + } + svid_begin_reset(); + + /* The reset wait is almost the 44.1 kHz PCM DMA emergency-buffer + * duration. Service DMA during the wait, but keep every SVID register + * write in the original atomic initialization sequence. */ + restore_irq(oldlevel); + udelay(10000); + + oldlevel = disable_irq_save(); + svid_finish_reset(); + svid_select_composite(); + svid_start_pipeline(); + restore_irq(oldlevel); + + svid_active = true; + svid_layer_active = false; + svid_layer_planar = false; + svid_mirror_enabled = false; + return true; +} + +static bool svid_show_framebuffer(const void *framebuffer, + int width, int height) +{ + if (framebuffer == NULL || width != LCD_WIDTH || height != LCD_HEIGHT) + return false; + + if (!svid_enable_sync()) + return false; + + svid_set_bus_boost(true); + + int oldlevel = disable_irq_save(); + SVID_REG(SVID_ROUTER_BASE, SVID_MXR_CONFIG) &= + ~SVID_MXR_ENABLE_MASK; + SVID_REG(SVID_COMPOSITOR_BASE, SVID_VP_ENABLE) &= + ~SVID_VP_ENABLE_ON; + svid_mixer_commit(); + restore_irq(oldlevel); + + svid_copy_framebuffer_planar(framebuffer, width, height); + + oldlevel = disable_irq_save(); + + uintptr_t luma = (uintptr_t)svid_output_framebuffer; + uintptr_t cb = luma + SVID_PLANAR_Y_SIZE; + uintptr_t cr = cb + SVID_PLANAR_C_SIZE; + + /* Exact RetailOS private format-8 setup: descriptor 0 -> 0x28, + * descriptor 2 -> 0x2c, descriptor 1 -> 0x30, zero -> 0x34, spans width + * and width/2, and plane mode 1. Physical DCP750 color qualification + * fixes this private ordering as Y, Cb, and Cr for our generated frame. */ + SVID_REG(SVID_COMPOSITOR_BASE, SVID_VP_MODE) = + SVID_VP_MODE_RETAIL_FMT8; + SVID_REG(SVID_COMPOSITOR_BASE, SVID_VP_PLANE0_PTR) = luma; + SVID_REG(SVID_COMPOSITOR_BASE, SVID_VP_PLANE2_PTR) = cb; + SVID_REG(SVID_COMPOSITOR_BASE, SVID_VP_UNUSED_PTR) = 0; + SVID_REG(SVID_COMPOSITOR_BASE, SVID_VP_PLANE1_PTR) = cr; + SVID_REG(SVID_COMPOSITOR_BASE, SVID_VP_IMG_WIDTH) = + SVID_PLANAR_Y_WIDTH; + SVID_REG(SVID_COMPOSITOR_BASE, SVID_VP_IMG_HEIGHT) = + SVID_PLANAR_Y_HEIGHT; + SVID_REG(SVID_COMPOSITOR_BASE, SVID_VP_SRC_H_POS) = 0; + SVID_REG(SVID_COMPOSITOR_BASE, SVID_VP_SRC_V_POS) = 0; + SVID_REG(SVID_COMPOSITOR_BASE, SVID_VP_SRC_WIDTH) = + SVID_PLANAR_Y_WIDTH; + SVID_REG(SVID_COMPOSITOR_BASE, SVID_VP_SRC_HEIGHT) = + SVID_PLANAR_SOURCE_HEIGHT; + SVID_REG(SVID_COMPOSITOR_BASE, SVID_VP_DST_H_POS) = + SVID_UI_DESTINATION_X; + /* RetailOS's direct geometry path writes caller-provided destination Y + * and height verbatim on this exact SoC. + * Applying the later S5P driver's field-line /2 conversion produces the + * physically observed upper-half-only picture. */ + SVID_REG(SVID_COMPOSITOR_BASE, SVID_VP_DST_V_POS) = + SVID_UI_DESTINATION_Y; + SVID_REG(SVID_COMPOSITOR_BASE, SVID_VP_DST_WIDTH) = + SVID_UI_DESTINATION_WIDTH; + SVID_REG(SVID_COMPOSITOR_BASE, SVID_VP_DST_HEIGHT) = + SVID_UI_DESTINATION_HEIGHT; + SVID_REG(SVID_COMPOSITOR_BASE, SVID_VP_H_RATIO) = + ((SVID_PLANAR_Y_WIDTH << 12) / SVID_UI_DESTINATION_WIDTH) >> 3; + /* Destination geometry remains in RetailOS full-frame coordinates. */ + SVID_REG(SVID_COMPOSITOR_BASE, SVID_VP_V_RATIO) = + ((SVID_PLANAR_Y_HEIGHT << 12) / SVID_UI_DESTINATION_HEIGHT) >> 4; + SVID_REG(SVID_COMPOSITOR_BASE, SVID_VP_PLANE_MODE) = + SVID_VP_PLANE_PLANAR; + SVID_REG(SVID_COMPOSITOR_BASE, SVID_VP_LUMA_SPAN) = + SVID_PLANAR_Y_WIDTH; + SVID_REG(SVID_COMPOSITOR_BASE, SVID_VP_CHROMA_SPAN) = + SVID_PLANAR_C_WIDTH; + SVID_REG(SVID_COMPOSITOR_BASE, SVID_VP_ENDIAN_MODE) = + SVID_VP_ENDIAN_LITTLE; + + SVID_REG(SVID_ROUTER_BASE, SVID_MXR_VIDEO_CONFIG) = 0; + SVID_REG(SVID_ROUTER_BASE, SVID_MXR_BG_COLOR0) = 0x00108080u; + SVID_REG(SVID_ROUTER_BASE, SVID_MXR_BG_COLOR1) = 0x00108080u; + SVID_REG(SVID_ROUTER_BASE, SVID_MXR_BG_COLOR2) = 0x00108080u; + SVID_REG(SVID_COMPOSITOR_BASE, SVID_VP_ENABLE) |= + SVID_VP_ENABLE_ON; + SVID_REG(SVID_ROUTER_BASE, SVID_MXR_CONFIG) = + (SVID_REG(SVID_ROUTER_BASE, SVID_MXR_CONFIG) & + ~SVID_MXR_ENABLE_MASK) | SVID_MXR_VIDEO_ENABLE; + SVID_REG(SVID_ROUTER_BASE, SVID_MXR_STATUS) = + SVID_MXR_STATUS_IDLE | SVID_MXR_STATUS_SYNC | SVID_MXR_STATUS_RUN; + svid_mixer_commit(); + + restore_irq(oldlevel); + svid_layer_planar = true; + svid_mirror_enabled = true; + svid_layer_active = true; + return true; +} + +static bool svid_stop_block(uintptr_t base) +{ + SVID_REG(base, 0) &= ~1u; + for (unsigned i = 0; i < 10; i++) + { + sleep(1); + if (SVID_REG(base, 0) & 2) + return true; + } + return false; +} + +bool videoout_active(void) +{ + if (svid_policy_pending) + svid_apply_policy(); + + return svid_active && svid_layer_active; +} + +bool videoout_lcd_clock_required(void) +{ + return svid_platform_saved; +} + +static void svid_commit_planar_rect(int x, int y, int width, int height) +{ + uint8_t *luma = (uint8_t *)svid_output_framebuffer; + uint8_t *cb = luma + SVID_PLANAR_Y_SIZE; + uint8_t *cr = cb + SVID_PLANAR_C_SIZE; + int first_chroma_column = x / 2; + int last_chroma_column = (x + width - 1) / 2; + int first_chroma_row = y / 2; + int last_chroma_row = (y + height - 1) / 2; + unsigned luma_span = (height - 1) * SVID_PLANAR_Y_WIDTH + width; + unsigned chroma_width = last_chroma_column - first_chroma_column + 1; + unsigned chroma_span = + (last_chroma_row - first_chroma_row) * SVID_PLANAR_C_WIDTH + + chroma_width; + + /* One clean per touched plane replaces a clean/barrier for every row. + * The spans include untouched bytes between partial rows but never leave + * the fixed output buffer. */ + commit_dcache_range(luma + y * SVID_PLANAR_Y_WIDTH + x, luma_span); + commit_dcache_range(cb + first_chroma_row * SVID_PLANAR_C_WIDTH + + first_chroma_column, chroma_span); + commit_dcache_range(cr + first_chroma_row * SVID_PLANAR_C_WIDTH + + first_chroma_column, chroma_span); +} + +static void svid_copy_yuv_plane(uint8_t *destination, + unsigned destination_stride, + const uint8_t *source, + unsigned source_stride, + unsigned width, unsigned height) +{ + if (width == destination_stride && width == source_stride) + { + memcpy(destination, source, width * height); + return; + } + + while (height-- > 0) + { + memcpy(destination, source, width); + destination += destination_stride; + source += source_stride; + } +} + +bool videoout_mirror_yuv420(const unsigned char *source_luma, + const unsigned char *source_cb, + const unsigned char *source_cr, + int source_x, int source_y, + int source_stride, + int x, int y, int width, int height) +{ + uint8_t *luma = (uint8_t *)svid_output_framebuffer; + uint8_t *cb = luma + SVID_PLANAR_Y_SIZE; + uint8_t *cr = cb + SVID_PLANAR_C_SIZE; + + if (!videoout_active() || !svid_mirror_enabled || + !svid_layer_planar || source_luma == NULL || source_cb == NULL || + source_cr == NULL || source_x < 0 || source_y < 0 || + source_stride < width || x < 0 || y < 0 || width <= 0 || + height <= 0 || x + width > LCD_WIDTH || y + height > LCD_HEIGHT || + source_x + width > source_stride || + ((source_x | source_y | source_stride | x | y | width | height) & 1)) + return false; + + source_luma += source_y * source_stride + source_x; + source_cb += (source_y / 2) * (source_stride / 2) + source_x / 2; + source_cr += (source_y / 2) * (source_stride / 2) + source_x / 2; + + svid_copy_yuv_plane(luma + y * SVID_PLANAR_Y_WIDTH + x, + SVID_PLANAR_Y_WIDTH, source_luma, source_stride, + width, height); + svid_copy_yuv_plane(cb + (y / 2) * SVID_PLANAR_C_WIDTH + x / 2, + SVID_PLANAR_C_WIDTH, source_cb, source_stride / 2, + width / 2, height / 2); + svid_copy_yuv_plane(cr + (y / 2) * SVID_PLANAR_C_WIDTH + x / 2, + SVID_PLANAR_C_WIDTH, source_cr, source_stride / 2, + width / 2, height / 2); + svid_commit_planar_rect(x, y, width, height); + return true; +} + +static void svid_mirror_rgb565_planar_even(const uint16_t *source, + int x, int y, int width, + int height, int stride) +{ + uint8_t *luma = (uint8_t *)svid_output_framebuffer; + uint8_t *cb = luma + SVID_PLANAR_Y_SIZE; + uint8_t *cr = cb + SVID_PLANAR_C_SIZE; + + for (int row = 0; row < height; row += 2) + { + const uint16_t *source0 = source + row * stride; + const uint16_t *source1 = source0 + stride; + uint8_t *luma0 = luma + (y + row) * SVID_PLANAR_Y_WIDTH + x; + uint8_t *luma1 = luma0 + SVID_PLANAR_Y_WIDTH; + uint8_t *cb0 = cb + ((y + row) / 2) * SVID_PLANAR_C_WIDTH + x / 2; + uint8_t *cr0 = cr + ((y + row) / 2) * SVID_PLANAR_C_WIDTH + x / 2; + + for (int column = 0; column < width; column += 2) + { + uint32_t pixel00 = svid_rgb565_to_ycbcr(source0[column]); + uint32_t pixel01 = svid_rgb565_to_ycbcr(source0[column + 1]); + uint32_t pixel10 = svid_rgb565_to_ycbcr(source1[column]); + uint32_t pixel11 = svid_rgb565_to_ycbcr(source1[column + 1]); + + luma0[column] = pixel00; + luma0[column + 1] = pixel01; + luma1[column] = pixel10; + luma1[column + 1] = pixel11; + cb0[column / 2] = (((pixel00 >> 8) & 0xff) + + ((pixel01 >> 8) & 0xff) + + ((pixel10 >> 8) & 0xff) + + ((pixel11 >> 8) & 0xff) + 2) >> 2; + cr0[column / 2] = (((pixel00 >> 16) & 0xff) + + ((pixel01 >> 16) & 0xff) + + ((pixel10 >> 16) & 0xff) + + ((pixel11 >> 16) & 0xff) + 2) >> 2; + } + } +} + +static void svid_mirror_rgb565_planar(const uint16_t *source, int x, int y, + int width, int height, int stride) +{ + uint8_t *luma = (uint8_t *)svid_output_framebuffer; + uint8_t *cb = luma + SVID_PLANAR_Y_SIZE; + uint8_t *cr = cb + SVID_PLANAR_C_SIZE; + + /* LCD and decoded-video frames use even 4:2:0 rectangles. Convert each + * source pixel exactly once, writing its luma and contributing its chroma + * in the same pass. */ + if (((x | y | width | height) & 1) == 0) + { + svid_mirror_rgb565_planar_even(source, x, y, width, height, stride); + svid_commit_planar_rect(x, y, width, height); + return; + } + + for (int row = 0; row < height; row++) + { + uint8_t *luma0 = luma + (y + row) * SVID_PLANAR_Y_WIDTH + x; + const uint16_t *source_row = source + row * stride; + + for (int column = 0; column < width; column++) + { + luma0[column] = svid_rgb565_to_ycbcr(source_row[column]); + } + } + + int first_chroma_column = x / 2; + int last_chroma_column = (x + width - 1) / 2; + int first_chroma_row = y / 2; + int last_chroma_row = (y + height - 1) / 2; + for (int chroma_row = first_chroma_row; + chroma_row <= last_chroma_row; chroma_row++) + { + int source_y0 = MAX(chroma_row * 2, y) - y; + int source_y1 = MIN(chroma_row * 2 + 1, y + height - 1) - y; + const uint16_t *source_row0 = source + source_y0 * stride; + const uint16_t *source_row1 = source + source_y1 * stride; + uint8_t *cb0 = cb + chroma_row * SVID_PLANAR_C_WIDTH + + first_chroma_column; + uint8_t *cr0 = cr + chroma_row * SVID_PLANAR_C_WIDTH + + first_chroma_column; + + for (int chroma_column = first_chroma_column; + chroma_column <= last_chroma_column; chroma_column++) + { + int source_x0 = MAX(chroma_column * 2, x) - x; + int source_x1 = MIN(chroma_column * 2 + 1, + x + width - 1) - x; + int output_column = chroma_column - first_chroma_column; + uint32_t pixel00 = + svid_rgb565_to_ycbcr(source_row0[source_x0]); + uint32_t pixel01 = + svid_rgb565_to_ycbcr(source_row0[source_x1]); + uint32_t pixel10 = + svid_rgb565_to_ycbcr(source_row1[source_x0]); + uint32_t pixel11 = + svid_rgb565_to_ycbcr(source_row1[source_x1]); + + cb0[output_column] = (((pixel00 >> 8) & 0xff) + + ((pixel01 >> 8) & 0xff) + + ((pixel10 >> 8) & 0xff) + + ((pixel11 >> 8) & 0xff) + 2) >> 2; + cr0[output_column] = (((pixel00 >> 16) & 0xff) + + ((pixel01 >> 16) & 0xff) + + ((pixel10 >> 16) & 0xff) + + ((pixel11 >> 16) & 0xff) + 2) >> 2; + } + } + + svid_commit_planar_rect(x, y, width, height); +} + +void videoout_mirror_rgb565(const void *source, int x, int y, + int width, int height, int stride) +{ + const uint16_t *src = source; + + if (!videoout_active() || !svid_mirror_enabled || source == NULL || + !svid_layer_planar || x < 0 || y < 0 || width <= 0 || height <= 0 || + stride < width || x + width > LCD_WIDTH || + y + height > LCD_HEIGHT) + return; + + svid_mirror_rgb565_planar(src, x, y, width, height, stride); +} + +bool videoout_disable(void) +{ + if (!svid_active) + { + svid_set_bus_boost(false); + return true; + } + + SVID_REG(SVID_ROUTER_BASE, SVID_MXR_CONFIG) &= + ~SVID_MXR_ENABLE_MASK; + SVID_REG(SVID_ENCODER_BASE, 0x03c) &= ~0xfu; + bool clean = svid_stop_block(SVID_ENCODER_BASE); + clean = svid_stop_block(SVID_ROUTER_BASE) && clean; + clean = svid_stop_block(SVID_COMPOSITOR_BASE) && clean; + + int oldlevel = disable_irq_save(); + svid_platform_restore(); + restore_irq(oldlevel); + + svid_active = false; + svid_layer_active = false; + svid_layer_planar = false; + svid_mirror_enabled = false; + lcd_videoout_clock_release(); + svid_set_bus_boost(false); + return clean; +} + +static void svid_apply_policy(void) +{ + /* Off is fully dormant: do not even perform the one-time accessory ADC + * transaction. A deferred request remains available if the user later + * selects Auto or On while the same dock is still connected. */ + if (svid_mode != VIDEOOUT_OFF) + svid_identify_accessory(); + + /* Auto requires the qualified DCP750 signature. On accepts a dock only + * after accessory identification has completed; PENDING must not bypass + * the startup window and start SVID while the connector/iAP state is still + * settling. NONE must never retain CPU/LCD clocks while undocked. */ + bool enable = + (svid_mode == VIDEOOUT_AUTO && + svid_accessory == VIDEOOUT_ACCESSORY_VIDEO) || + (svid_mode == VIDEOOUT_ON && + (svid_accessory == VIDEOOUT_ACCESSORY_VIDEO || + svid_accessory == VIDEOOUT_ACCESSORY_OTHER)); + + svid_policy_pending = false; + + if (enable) + { + if ((!svid_active || !svid_layer_active) && + svid_policy_framebuffer != NULL) + svid_show_framebuffer(svid_policy_framebuffer, + svid_policy_width, + svid_policy_height); + } + else if (svid_active) + { + videoout_disable(); + } +} + +void videoout_request_accessory_identification(void) +{ + /* Called by the serial tick task: stores only, no ADC/I2C or SVID work. */ + svid_identification_pending = true; + if (svid_mode != VIDEOOUT_OFF) + svid_policy_pending = true; +} + +void videoout_set_mode(enum videoout_mode mode, + const void *framebuffer, + int width, int height) +{ + if (mode > VIDEOOUT_ON) + mode = VIDEOOUT_OFF; + + svid_mode = mode; + svid_policy_framebuffer = framebuffer; + svid_policy_width = width; + svid_policy_height = height; + svid_policy_pending = true; + svid_apply_policy(); +} + +void videoout_accessory_state(enum videoout_accessory accessory) +{ + if (accessory > VIDEOOUT_ACCESSORY_OTHER) + accessory = VIDEOOUT_ACCESSORY_OTHER; + + if (accessory != VIDEOOUT_ACCESSORY_PENDING) + svid_identification_pending = false; + + if (svid_accessory != accessory) + { + svid_accessory = accessory; + svid_policy_pending = true; + } +} diff --git a/firmware/target/arm/s5l8702/ipod6g/vpu-6g.c b/firmware/target/arm/s5l8702/ipod6g/vpu-6g.c new file mode 100644 index 0000000000..b5295b7059 --- /dev/null +++ b/firmware/target/arm/s5l8702/ipod6g/vpu-6g.c @@ -0,0 +1,71 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * S5L8702 VPU-B IRQ 35 completion handler. + * + * IRQ 35 is VIC1 bit 3 and is asserted when VPU-B completes a decode. + * The handler captures the completion status, acknowledges the edge, and + * releases a semaphore for the decoder driver. + * + * Copyright (C) 2025-2026 David Cormier + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#include "config.h" +#include "system.h" +#include "kernel.h" +#include "s5l87xx.h" + +#define VPU_IRQ_BIT (1u << 3) + +static struct semaphore vpu_complete; +static volatile uint32_t vpu_last_status1; + +void vpu_irq_init(void) +{ + semaphore_init(&vpu_complete, 1, 0); + vpu_last_status1 = 0; + VIC1INTENCLEAR = VPU_IRQ_BIT; + VIC1EDGE0 |= VPU_IRQ_BIT; + VIC1EDGE1 = VPU_IRQ_BIT; +} + +void vpu_irq_arm(void) +{ + semaphore_wait(&vpu_complete, 0); + VIC1EDGE1 = VPU_IRQ_BIT; + VIC1INTENABLE = VPU_IRQ_BIT; +} + +int vpu_irq_wait(int timeout_ticks) +{ + int ret = semaphore_wait(&vpu_complete, timeout_ticks); + VIC1INTENCLEAR = VPU_IRQ_BIT; + return ret; +} + +uint32_t vpu_irq_status1(void) +{ + return vpu_last_status1; +} + +void ICODE_ATTR INT_IRQ35(void) +{ + vpu_last_status1 = VPU_STATUS1; + VIC1EDGE1 = VPU_IRQ_BIT; + VIC1INTENCLEAR = VPU_IRQ_BIT; + semaphore_release(&vpu_complete); +} diff --git a/firmware/target/arm/s5l8702/ipod6g/vpu-6g.h b/firmware/target/arm/s5l8702/ipod6g/vpu-6g.h new file mode 100644 index 0000000000..a3a64e8aff --- /dev/null +++ b/firmware/target/arm/s5l8702/ipod6g/vpu-6g.h @@ -0,0 +1,48 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * S5L8702 VPU-B IRQ completion for H.264 hardware decoder. + * + * VPU-B asserts IRQ 35 (VIC1 bit 3, edge-triggered) on decode completion. + * These functions provide semaphore-based synchronous wait, replacing + * the sleep(200ms) polling approach. + * + * Copyright (C) 2025-2026 David Cormier + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#ifndef VPU_6G_H +#define VPU_6G_H + +#include + +/* Initialize VPU-B IRQ 35: setup semaphore, configure edge trigger. + * Call once before first decode. */ +void vpu_irq_init(void); + +/* Arm for next decode: drain stale semaphore signal, clear pending + * edge, enable IRQ 35. Call BEFORE triggering VPU decode. */ +void vpu_irq_arm(void); + +/* Wait for VPU decode completion. Blocks until ISR fires or timeout. + * Disables IRQ 35 after return. Returns OBJ_WAIT_TIMEDOUT on timeout, + * OBJ_WAIT_SUCCEEDED on success. */ +int vpu_irq_wait(int timeout_ticks); + +/* Get STATUS1 value captured by ISR during last completion. */ +uint32_t vpu_irq_status1(void); + +#endif /* VPU_6G_H */ diff --git a/firmware/target/arm/s5l8702/ipod6g/vpu-h264-6g.c b/firmware/target/arm/s5l8702/ipod6g/vpu-h264-6g.c new file mode 100644 index 0000000000..9410e670d0 --- /dev/null +++ b/firmware/target/arm/s5l8702/ipod6g/vpu-h264-6g.c @@ -0,0 +1,1075 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2025-2026 David Cormier + * + * S5L8702 VPU-B H.264 Baseline hardware decoder implementation. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#include "config.h" +#include "system.h" +#include "kernel.h" +#include "s5l87xx.h" +#include "vpu-h264-6g.h" +#include "vpu-6g.h" + +#include + +#define VPU_CONFIG_CONST 0x82625A00 +#define VPU_SLICE_CONST 0x00110C85 +#define VPU_TRIGGER_BITS 0x88003001 +#define VPU_MODE_H264 (1u << 0) +#define VPU_CLOCK_GATE_BIT (1u << 17) +#define VPP_CLOCK_GATE_MASK (7u << 14) + +#define ALIGN32(x) CACHEALIGN_UP(x) +#define ALIGN4K(x) ALIGN_UP((x), 0x1000) +#define PHYS(x) ((uint32_t)(uintptr_t)S5L8702_PHYSICAL_ADDR(x)) +#define UNCACHED(x) S5L8702_UNCACHED_ADDR(x) + +#define BS_DMA_SIZE 262144 +#define SLICE_DESC_SIZE 320 +#define SLICE_DESC_WORDS 8 +#define VPU_MAX_SLICES \ + (SLICE_DESC_SIZE / (SLICE_DESC_WORDS * sizeof(uint32_t))) + +/* ---- Bitstream reader (exp-Golomb) ---- */ + +struct bs { + const uint8_t *buf; + unsigned long bit_offset; + unsigned long bit_length; + int error; +}; + +static uint32_t bs_un(struct bs *b, int n) +{ + uint32_t val = 0; + int i; + + if (n < 0 || n > 32) + { + b->error = 1; + return 0; + } + for (i = 0; i < n; i++) + { + if (b->bit_offset >= b->bit_length) + { + b->error = 1; + return 0; + } + unsigned long byte_pos = b->bit_offset >> 3; + unsigned int bit_pos = 7 - (b->bit_offset & 7); + val = (val << 1) | ((b->buf[byte_pos] >> bit_pos) & 1); + b->bit_offset++; + } + return val; +} + +static uint32_t bs_u1(struct bs *b) { return bs_un(b, 1); } +static uint32_t bs_u8(struct bs *b) { return bs_un(b, 8); } + +static uint32_t bs_ue(struct bs *b) +{ + int lz = 0; + while (bs_u1(b) == 0 && lz < 31) + lz++; + if (b->error || lz >= 31) + { + b->error = 1; + return 0; + } + if (lz == 0) + return 0; + if (b->bit_offset >= b->bit_length) + { + b->error = 1; + return 0; + } + return (UINT32_C(1) << lz) - 1 + bs_un(b, lz); +} + +static int bs_se(struct bs *b) +{ + uint32_t v = bs_ue(b); + return (v & 1) ? (int)((v + 1) >> 1) : -(int)(v >> 1); +} + +/* ---- EBSP to RBSP conversion ---- */ + +static int ebsp_to_rbsp(uint8_t *dst, const uint8_t *src, int src_len) +{ + int di = 0, si; + for (si = 0; si < src_len; si++) + { + if (si + 2 < src_len && src[si] == 0 && src[si+1] == 0 && + src[si+2] == 3) + { + dst[di++] = 0; + dst[di++] = 0; + si += 2; + } + else + { + dst[di++] = src[si]; + } + } + return di; +} + +/* Map an RBSP byte offset back to the corresponding EBSP byte offset. + * The VPU expects EBSP data (handles EPBs internally), so after parsing + * the slice header in RBSP space we need to find where the slice body + * starts in the original EBSP stream. */ +static int map_rbsp_to_ebsp(const uint8_t *ebsp, int ebsp_len, int rbsp_pos) +{ + int ri = 0, si = 0; + while (si < ebsp_len && ri < rbsp_pos) + { + if (si + 2 < ebsp_len && + ebsp[si] == 0 && ebsp[si+1] == 0 && ebsp[si+2] == 3) + { + if (ri + 1 >= rbsp_pos) + return si + (rbsp_pos - ri); + ri += 2; + si += 3; + } + else + { + ri++; + si++; + } + } + return si; +} + +/* ---- H.264 header structures ---- */ + +struct sps { + int profile_idc, level_idc; + int pic_width_in_mbs_minus1, pic_height_in_map_units_minus1; + int log2_max_frame_num_minus4; + int pic_order_cnt_type, log2_max_pic_order_cnt_lsb_minus4; + int max_num_ref_frames; + int gaps_allowed; + int frame_mbs_only; + int crop_left, crop_right, crop_top, crop_bottom; +}; + +struct pps { + int pic_init_qp_minus26, chroma_qp_index_offset; + int deblocking_filter_control_present_flag, weighted_pred_flag; + int num_ref_idx_l0_default_active_minus1; + int entropy_coding_mode; + int bottom_field_pic_order; + int num_slice_groups_minus1; + int weighted_bipred_idc; +}; + +struct slice_hdr { + int first_mb_in_slice, slice_type, slice_qp_delta; + int disable_deblocking_filter_idc; + int alpha_c0_offset_div2, beta_offset_div2; + int frame_num; + int num_ref_idx_l0_active_minus1; + int ref_list_modification; + int adaptive_ref_marking; + unsigned long bits_consumed; +}; + +/* ---- H.264 header parsers ---- */ + +static void parse_sps(struct sps *sps, struct bs *b) +{ + b->bit_offset = 8; + sps->profile_idc = bs_u8(b); + bs_un(b, 8); + sps->level_idc = bs_u8(b); + bs_ue(b); + + if (sps->profile_idc != 66) + { + b->error = 1; + return; + } + + sps->log2_max_frame_num_minus4 = bs_ue(b); + sps->pic_order_cnt_type = bs_ue(b); + if (sps->pic_order_cnt_type != 0) + { + b->error = 1; + return; + } + sps->log2_max_pic_order_cnt_lsb_minus4 = bs_ue(b); + sps->max_num_ref_frames = bs_ue(b); + sps->gaps_allowed = bs_u1(b); + sps->pic_width_in_mbs_minus1 = bs_ue(b); + sps->pic_height_in_map_units_minus1 = bs_ue(b); + sps->frame_mbs_only = bs_u1(b); + if (!sps->frame_mbs_only) + bs_u1(b); + bs_u1(b); /* direct_8x8_inference_flag */ + sps->crop_left = 0; + sps->crop_right = 0; + sps->crop_top = 0; + sps->crop_bottom = 0; + if (bs_u1(b)) /* frame_cropping_flag */ + { + uint32_t crop_left = bs_ue(b); + uint32_t crop_right = bs_ue(b); + uint32_t crop_top = bs_ue(b); + uint32_t crop_bottom = bs_ue(b); + + /* Baseline profile is always progressive 4:2:0 here, so both crop + * units are two pixels. */ + if (crop_left > 0x7fff || crop_right > 0x7fff || + crop_top > 0x7fff || crop_bottom > 0x7fff) + { + b->error = 1; + return; + } + sps->crop_left = (int)crop_left * 2; + sps->crop_right = (int)crop_right * 2; + sps->crop_top = (int)crop_top * 2; + sps->crop_bottom = (int)crop_bottom * 2; + } +} + +static void parse_pps(struct pps *pps, struct bs *b) +{ + b->bit_offset = 8; + bs_ue(b); bs_ue(b); + pps->entropy_coding_mode = bs_u1(b); + pps->bottom_field_pic_order = bs_u1(b); + pps->num_slice_groups_minus1 = bs_ue(b); + pps->num_ref_idx_l0_default_active_minus1 = bs_ue(b); + bs_ue(b); + pps->weighted_pred_flag = bs_u1(b); + pps->weighted_bipred_idc = bs_un(b, 2); + pps->pic_init_qp_minus26 = bs_se(b); + bs_se(b); + pps->chroma_qp_index_offset = bs_se(b); + pps->deblocking_filter_control_present_flag = bs_u1(b); +} + +static bool pps_supported(const struct pps *pps) +{ + return !pps->entropy_coding_mode && + pps->num_slice_groups_minus1 == 0 && + !pps->weighted_pred_flag && pps->weighted_bipred_idc == 0 && + pps->num_ref_idx_l0_default_active_minus1 == 0 && + pps->pic_init_qp_minus26 >= -26 && + pps->pic_init_qp_minus26 <= 25 && + pps->chroma_qp_index_offset >= -12 && + pps->chroma_qp_index_offset <= 12; +} + +static void parse_slice_header(const struct sps *sps, const struct pps *pps, + struct bs *b, int nal_type, int nal_ref_idc, + struct slice_hdr *sh) +{ + b->bit_offset = 8; + sh->first_mb_in_slice = bs_ue(b); + sh->slice_type = bs_ue(b); + if (sh->slice_type >= 5) + sh->slice_type -= 5; + bs_ue(b); /* pic_parameter_set_id */ + sh->frame_num = bs_un(b, sps->log2_max_frame_num_minus4 + 4); + + if (nal_type == 5) + bs_ue(b); /* idr_pic_id */ + if (sps->pic_order_cnt_type == 0) + { + bs_un(b, sps->log2_max_pic_order_cnt_lsb_minus4 + 4); + /* QuickTime 7.6.6's iPod preset sets + * bottom_field_pic_order_in_frame_present_flag even though the + * sequence is progressive, and writes delta_pic_order_cnt_bottom. */ + if (pps->bottom_field_pic_order) + bs_se(b); + } + + sh->num_ref_idx_l0_active_minus1 = + pps->num_ref_idx_l0_default_active_minus1; + sh->ref_list_modification = 0; + sh->adaptive_ref_marking = 0; + if (sh->slice_type == 0 || sh->slice_type == 1) + { + if (bs_u1(b)) + { + sh->num_ref_idx_l0_active_minus1 = bs_ue(b); + if (sh->slice_type == 1) + bs_ue(b); + } + } + + /* ref_pic_list_modification */ + if (sh->slice_type == 0 || sh->slice_type == 1) + { + if (bs_u1(b)) + { + uint32_t idc; + sh->ref_list_modification = 1; + do { + idc = bs_ue(b); + if (idc == 0 || idc == 1) + bs_ue(b); + else if (idc == 2) + bs_ue(b); + } while (idc != 3 && b->bit_offset < b->bit_length); + } + if (sh->slice_type == 1) + { + if (bs_u1(b)) + { + uint32_t idc; + sh->ref_list_modification = 1; + do { + idc = bs_ue(b); + if (idc == 0 || idc == 1) + bs_ue(b); + else if (idc == 2) + bs_ue(b); + } while (idc != 3 && b->bit_offset < b->bit_length); + } + } + } + + /* dec_ref_pic_marking */ + if (nal_ref_idc != 0) + { + if (nal_type == 5) + { + bs_u1(b); bs_u1(b); + } + else + { + if (bs_u1(b)) + { + uint32_t mmco; + sh->adaptive_ref_marking = 1; + do { + mmco = bs_ue(b); + if (mmco == 1 || mmco == 3) + bs_ue(b); + if (mmco == 2) + bs_ue(b); + if (mmco == 3 || mmco == 6) + bs_ue(b); + if (mmco == 4) + bs_ue(b); + } while (mmco != 0); + } + } + } + + sh->slice_qp_delta = bs_se(b); + sh->disable_deblocking_filter_idc = 0; + sh->alpha_c0_offset_div2 = 0; + sh->beta_offset_div2 = 0; + + if (pps->deblocking_filter_control_present_flag) + { + sh->disable_deblocking_filter_idc = bs_ue(b); + if (sh->disable_deblocking_filter_idc != 1) + { + sh->alpha_c0_offset_div2 = bs_se(b); + sh->beta_offset_div2 = bs_se(b); + } + } + + sh->bits_consumed = b->bit_offset; +} + +/* ---- Slice descriptor builder ---- */ + +static void build_slice_descriptor(uint32_t *desc, + int first_mb, + int mb_count, + int slice_type, + int slice_qp_y, + int chroma_qp_off, + int weighted_pred, + int num_ref_l0, + int deblk_idc, + int alpha_off, + int beta_off, + int bit_offset, + uint32_t bs_phys_addr, + int bs_length) +{ + /* Word 0 carries the first macroblock and the number covered by this + * descriptor. Apple's iPod preset emits two half-picture slices. */ + desc[0] = ((first_mb & 0x7FF) << 21) + | ((mb_count & 0x7FF) << 10) + | ((slice_type & 0xF) << 6) + | (slice_qp_y & 0x3F); + + desc[1] = ((chroma_qp_off & 0xFF) << 15) + | ((weighted_pred & 1) << 14) + | ((num_ref_l0 & 0xF) << 10) + | ((deblk_idc & 0x3) << 8) + | ((alpha_off & 0xF) << 4) + | (beta_off & 0xF); + + desc[2] = VPU_SLICE_CONST; + desc[3] = 0; + desc[4] = bit_offset & 0xFF; + desc[5] = bs_phys_addr; + desc[6] = bs_length; + desc[7] = 0; +} + +/* ---- VPU-B power management ---- */ + +static void vpub_power_on(void) +{ + uint16_t cg; + uint32_t pw; + + /* Gate VPP clocks + VPU-B clock as initial state */ + VPU_MODE &= ~VPU_MODE_H264; + PWRCON(0) |= VPU_CLOCK_GATE_BIT | VPP_CLOCK_GATE_MASK; + /* No delay: Apple FW, all Rockbox drivers use zero delay after PWRCON */ + + /* Enable CG16_SVID (PLL2 source, already locked as system clock) */ + cg = CG16_SVID; + cg &= ~(CG16_DISABLE_BIT | (CG16_SEL_MSK << CG16_SEL_POS)); + cg |= CG16_SEL_PLL2 << CG16_SEL_POS; + CG16_SVID = cg; + + /* Enable VPP clocks */ + pw = PWRCON(0); + PWRCON(0) = pw & ~VPP_CLOCK_GATE_MASK; + + /* H.264 mode */ + VPU_MODE |= VPU_MODE_H264; + + /* Zero stale registers */ + PWRCON(0) &= ~VPU_CLOCK_GATE_BIT; + { + volatile uint32_t *base = (volatile uint32_t *)VPU_BASE; + int i; + for (i = 0; i < 0x130/4; i++) + base[i] = 0; + } + PWRCON(0) |= VPU_CLOCK_GATE_BIT; + + /* Initialize IRQ 35 for completion signaling */ + vpu_irq_init(); +} + +static void vpub_power_off(void) +{ + VPU_MODE &= ~VPU_MODE_H264; + PWRCON(0) |= VPU_CLOCK_GATE_BIT | VPP_CLOCK_GATE_MASK; +} + +/* ---- VPU-B decode trigger ---- */ + +static int vpub_decode(uint32_t ctrl_phys, uint32_t desc_phys, + uint32_t y_phys, uint32_t cb_phys, uint32_t cr_phys, + const uint32_t *dpb_y, const uint32_t *dpb_cb, + const uint32_t *dpb_cr, int dpb_count, + int width_mbs, int height_mbs, + int pic_w, int num_slices) +{ + uint32_t val; + int ret, old_irq; + + /* Double clock enable (Apple: 0x1c0b94 + 0x1c0ba4) */ + old_irq = disable_irq_save(); + PWRCON(0) &= ~VPU_CLOCK_GATE_BIT; + restore_irq(old_irq); + old_irq = disable_irq_save(); + PWRCON(0) &= ~VPU_CLOCK_GATE_BIT; + restore_irq(old_irq); + + /* Re-set VPU_MODE every frame (Apple: 0x1c0bac) */ + VPU_MODE |= VPU_MODE_H264; + + /* vtable[0x48] = h264_vpu_stop_and_reset (0x001c0b4c) */ + { + volatile uint32_t *base = (volatile uint32_t *)VPU_BASE; + uint32_t ec_val; + int i; + ec_val = base[0xEC/4]; + base[0xEC/4] = ec_val & ~1u; + ec_val = base[0xEC/4]; + (void)ec_val; + for (i = 0; i < 0x130/4; i++) + base[i] = 0; + } + + /* Program registers (Apple's exact order from FUN_001c06ac) */ + VPU_CTRL_BUF = ctrl_phys; + val = VPU_DIMS; + val = (val & ~0x3F00) | ((height_mbs & 0x3F) << 8); + VPU_DIMS = val; + val = VPU_DIMS; + val = (val & ~0x003F) | (width_mbs & 0x3F); + VPU_DIMS = val; + val = VPU_CTRL; + val = (val & ~0x0FFE) | ((width_mbs * height_mbs * 2) & 0xFFE); + VPU_CTRL = val; + VPU_SLICE_DESC = desc_phys; + val = VPU_STRIDES; + val = (val & ~0x01FF0000) | (((pic_w / 2) & 0x1FF) << 16); + VPU_STRIDES = val; + val = VPU_STRIDES; + val = (val & ~0x000003FF) | (pic_w & 0x3FF); + VPU_STRIDES = val; + VPU_OUT_Y = y_phys; + VPU_OUT_CB = cb_phys; + VPU_OUT_CR = cr_phys; + VPU_CONFIG = VPU_CONFIG_CONST; + + /* Fill the DPB reference frame table with reference frames + * (newest at slot 0). Remaining slots get slot 0's data. */ + { + int dpb; + for (dpb = 0; dpb < dpb_count && dpb < 17; dpb++) + { + VPU_DPB_Y(dpb) = dpb_y[dpb]; + VPU_DPB_CB(dpb) = dpb_cb[dpb]; + VPU_DPB_CR(dpb) = dpb_cr[dpb]; + } + for (; dpb < 17; dpb++) + { + VPU_DPB_Y(dpb) = dpb_count > 0 ? dpb_y[0] : 0; + VPU_DPB_CB(dpb) = dpb_count > 0 ? dpb_cb[0] : 0; + VPU_DPB_CR(dpb) = dpb_count > 0 ? dpb_cr[0] : 0; + } + } + + /* Slice count + trigger */ + val = VPU_CTRL; + val = (val & ~0x07FF0000) | ((num_slices & 0x7FF) << 16); + VPU_CTRL = val; + + /* Arm IRQ, trigger, wait */ + vpu_irq_arm(); + VPU_CTRL = VPU_CTRL | VPU_TRIGGER_BITS; + + ret = vpu_irq_wait(HZ / 2); + if (ret == OBJ_WAIT_TIMEDOUT) + { + old_irq = disable_irq_save(); + PWRCON(0) |= VPU_CLOCK_GATE_BIT; + restore_irq(old_irq); + return -1; + } + + val = vpu_irq_status1(); + ret = 0; + if ((val << 3) >> 21) + ret = -2; + + if (ret == 0) + VPU_CONFIG = VPU_CONFIG_CONST; + + /* Disable clock */ + old_irq = disable_irq_save(); + PWRCON(0) |= VPU_CLOCK_GATE_BIT; + restore_irq(old_irq); + + return ret; +} + +/* ---- Internal context structure ---- */ + +#define MAX_DPB_FRAMES 5 /* up to 4 reference frames + 1 output */ + +struct vpu_h264 { + int max_w, max_h; + int pic_w, pic_h, pic_wmb, pic_hmb; + int display_w, display_h; + struct sps sps; + struct pps pps; + int have_sps, have_pps; + int cur_out; /* next output buffer index */ + int last_out; /* most recently decoded picture */ + int dpb_count; /* valid references in dpb_order */ + int dpb_order[MAX_DPB_FRAMES]; /* buffer indices, oldest first */ + uint8_t *ctrl_buf; + uint8_t *slice_desc; + uint8_t *bs_dma; + uint8_t *nalu_buf; + uint8_t *frame_y[MAX_DPB_FRAMES]; + uint8_t *frame_cb[MAX_DPB_FRAMES]; + uint8_t *frame_cr[MAX_DPB_FRAMES]; + int frame_y_size; + int frame_cb_size; + int frame_cr_size; +}; + +static int apply_sps_dimensions(struct vpu_h264 *v) +{ + int horizontal_crop; + int vertical_crop; + + if (v->sps.profile_idc != 66 || v->sps.level_idc > 30 || + v->sps.pic_order_cnt_type != 0 || + v->sps.log2_max_frame_num_minus4 < 0 || + v->sps.log2_max_frame_num_minus4 > 12 || + v->sps.log2_max_pic_order_cnt_lsb_minus4 < 0 || + v->sps.log2_max_pic_order_cnt_lsb_minus4 > 12 || + v->sps.max_num_ref_frames < 0 || + v->sps.max_num_ref_frames > 2 || v->sps.gaps_allowed || + !v->sps.frame_mbs_only || + v->sps.pic_width_in_mbs_minus1 < 0 || + v->sps.pic_width_in_mbs_minus1 >= v->max_w / 16 || + v->sps.pic_height_in_map_units_minus1 < 0 || + v->sps.pic_height_in_map_units_minus1 >= v->max_h / 16) + return -1; + + v->pic_wmb = v->sps.pic_width_in_mbs_minus1 + 1; + v->pic_hmb = v->sps.pic_height_in_map_units_minus1 + 1; + v->pic_w = v->pic_wmb * 16; + v->pic_h = v->pic_hmb * 16; + horizontal_crop = v->sps.crop_left + v->sps.crop_right; + vertical_crop = v->sps.crop_top + v->sps.crop_bottom; + v->display_w = v->pic_w - horizontal_crop; + v->display_h = v->pic_h - vertical_crop; + + if (horizontal_crop < 0 || horizontal_crop >= v->pic_w || + vertical_crop < 0 || vertical_crop >= v->pic_h || + v->display_w <= 0 || v->display_h <= 0 || + (v->display_w & 1) || (v->display_h & 1)) + return -1; + + v->frame_y_size = v->pic_w * v->pic_h; + v->frame_cb_size = (v->pic_w / 2) * (v->pic_h / 2); + v->frame_cr_size = v->frame_cb_size; + return 0; +} + +/* ---- Public API ---- */ + +size_t vpu_h264_buf_size(int max_w, int max_h) +{ + size_t sz = 0; + + if (max_w <= 0 || max_h <= 0 || max_w > 1280 || max_h > 720) + return 0; + sz += sizeof(struct vpu_h264) + 32; + sz += (size_t)max_w * max_h * 3 / 2 + 4096; /* ctrl_buf */ + sz += SLICE_DESC_SIZE + 32; + sz += BS_DMA_SIZE + 32; + sz += BS_DMA_SIZE + 32; /* nalu_buf */ + sz += ((size_t)max_w * max_h + 4096) * MAX_DPB_FRAMES; /* frame_y */ + sz += ((size_t)max_w * max_h / 4 + 32) * MAX_DPB_FRAMES; + sz += ((size_t)max_w * max_h / 4 + 32) * MAX_DPB_FRAMES; + return sz; +} + +struct vpu_h264 *vpu_h264_open(void *buf, size_t buf_size, + int max_w, int max_h) +{ + uint8_t *p = (uint8_t *)buf; + struct vpu_h264 *v; + int fy_sz, fc_sz, ctrl_sz; + + if (max_w <= 0 || max_h <= 0 || max_w > 1280 || max_h > 720) + return NULL; + if (!buf || buf_size < vpu_h264_buf_size(max_w, max_h)) + return NULL; + + v = (struct vpu_h264 *)ALIGN32(p); + p = (uint8_t *)v + sizeof(*v); + memset(v, 0, sizeof(*v)); + + v->max_w = max_w; + v->max_h = max_h; + + ctrl_sz = max_w * max_h * 3 / 2; + fy_sz = max_w * max_h; + fc_sz = max_w * max_h / 4; + + v->ctrl_buf = (uint8_t *)ALIGN4K(p); p = v->ctrl_buf + ctrl_sz; + v->slice_desc = (uint8_t *)ALIGN32(p); + p = v->slice_desc + SLICE_DESC_SIZE; + v->bs_dma = (uint8_t *)ALIGN32(p); p = v->bs_dma + BS_DMA_SIZE; + v->nalu_buf = (uint8_t *)ALIGN32(p); p = v->nalu_buf + BS_DMA_SIZE; + + { + int i; + for (i = 0; i < MAX_DPB_FRAMES; i++) + { + v->frame_y[i] = (uint8_t *)ALIGN4K(p); p = v->frame_y[i] + fy_sz; + v->frame_cb[i] = (uint8_t *)ALIGN32(p); p = v->frame_cb[i] + fc_sz; + v->frame_cr[i] = (uint8_t *)ALIGN32(p); p = v->frame_cr[i] + fc_sz; + } + } + + v->frame_y_size = fy_sz; + v->frame_cb_size = fc_sz; + v->frame_cr_size = fc_sz; + v->cur_out = 0; + v->dpb_count = 0; + + /* Zero control buffers */ + memset(v->ctrl_buf, 0, ctrl_sz); + memset(v->bs_dma, 0, BS_DMA_SIZE); + + /* Pre-fill frame buffers with neutral YCbCr (black) */ + { + int i; + for (i = 0; i < MAX_DPB_FRAMES; i++) + { + memset(v->frame_y[i], 0x10, fy_sz); + memset(v->frame_cb[i], 0x80, fc_sz); + memset(v->frame_cr[i], 0x80, fc_sz); + } + } + + /* Make all cached initialization visible before the VPU uses DMA. */ + commit_dcache(); + vpub_power_on(); + return v; +} + +int vpu_h264_configure(struct vpu_h264 *v, + const uint8_t *avcc, int avcc_len) +{ + int i, cnt, offset; + int nalu_len_size; + struct bs b; + + if (!v || !avcc || avcc_len < 7) + return -1; + + /* avcC structure: version(1) profile(1) compat(1) level(1) + * nalu_len_size(1) num_sps(1) [sps_len(2) sps_data]... + * num_pps(1) [pps_len(2) pps_data]... */ + nalu_len_size = (avcc[4] & 0x03) + 1; + (void)nalu_len_size; + + /* Parse SPS */ + cnt = avcc[5] & 0x1F; + offset = 6; + for (i = 0; i < cnt && offset + 2 <= avcc_len; i++) + { + int sps_len = (avcc[offset] << 8) | avcc[offset + 1]; + offset += 2; + if (sps_len < 1 || offset + sps_len > avcc_len || + (avcc[offset] & 0x9f) != 7) + return -1; + int rbsp_len = ebsp_to_rbsp(v->nalu_buf, avcc + offset, sps_len); + b.buf = v->nalu_buf; + b.bit_offset = 0; + b.bit_length = rbsp_len * 8; + b.error = 0; + parse_sps(&v->sps, &b); + + if (b.error || apply_sps_dimensions(v) < 0) + return -1; + v->have_sps = 1; + offset += sps_len; + } + + /* Parse PPS */ + if (offset >= avcc_len) + return -1; + cnt = avcc[offset++]; + for (i = 0; i < cnt && offset + 2 <= avcc_len; i++) + { + int pps_len = (avcc[offset] << 8) | avcc[offset + 1]; + offset += 2; + if (pps_len < 1 || offset + pps_len > avcc_len || + (avcc[offset] & 0x9f) != 8) + return -1; + int rbsp_len = ebsp_to_rbsp(v->nalu_buf, avcc + offset, pps_len); + b.buf = v->nalu_buf; + b.bit_offset = 0; + b.bit_length = rbsp_len * 8; + b.error = 0; + parse_pps(&v->pps, &b); + if (b.error || !pps_supported(&v->pps)) + return -1; + v->have_pps = 1; + offset += pps_len; + } + + return (v->have_sps && v->have_pps) ? 0 : -1; +} + +struct picture_slice { + const uint8_t *nalu; + int nalu_len; + int nal_type; + int nal_ref_idc; + struct slice_hdr header; +}; + +static int parse_picture_slice(struct vpu_h264 *v, const uint8_t *nalu, + int nalu_len, struct picture_slice *slice) +{ + struct bs b; + int rbsp_len; + + if (!v->have_sps || !v->have_pps || nalu_len < 1 || + (nalu[0] & 0x80) != 0) + return -1; + rbsp_len = ebsp_to_rbsp(v->nalu_buf, nalu, MIN(nalu_len, 256)); + slice->nalu = nalu; + slice->nalu_len = nalu_len; + slice->nal_type = v->nalu_buf[0] & 0x1f; + slice->nal_ref_idc = (v->nalu_buf[0] >> 5) & 3; + b.buf = v->nalu_buf; + b.bit_offset = 0; + b.bit_length = rbsp_len * 8; + b.error = 0; + parse_slice_header(&v->sps, &v->pps, &b, slice->nal_type, + slice->nal_ref_idc, &slice->header); + if (b.error || + (slice->header.slice_type != 0 && slice->header.slice_type != 2) || + slice->header.num_ref_idx_l0_active_minus1 != 0 || + slice->header.ref_list_modification || + slice->header.adaptive_ref_marking || + slice->header.disable_deblocking_filter_idc < 0 || + slice->header.disable_deblocking_filter_idc > 2 || + slice->header.alpha_c0_offset_div2 < -6 || + slice->header.alpha_c0_offset_div2 > 6 || + slice->header.beta_offset_div2 < -6 || + slice->header.beta_offset_div2 > 6) + return -1; + return 0; +} + +static int decode_picture(struct vpu_h264 *v, struct picture_slice *slices, + int slice_count) +{ + uint32_t dpb_y[MAX_DPB_FRAMES], dpb_cb[MAX_DPB_FRAMES]; + uint32_t dpb_cr[MAX_DPB_FRAMES]; + int total_mbs = v->pic_wmb * v->pic_hmb; + int out_buf = v->cur_out; + int dpb_fill = 0; + int dma_used = 0; + int is_idr; + int ret; + int i; + + if (slice_count < 1 || slice_count > (int)VPU_MAX_SLICES || + slices[0].header.first_mb_in_slice != 0) + return -1; + is_idr = slices[0].nal_type == 5; + for (i = 0; i < slice_count; i++) + { + if ((slices[i].nal_type != 1 && slices[i].nal_type != 5) || + (slices[i].nal_type == 5) != is_idr || + slices[i].nal_ref_idc != slices[0].nal_ref_idc || + slices[i].header.frame_num != slices[0].header.frame_num || + slices[i].header.slice_type != slices[0].header.slice_type || + slices[i].header.first_mb_in_slice < 0 || + slices[i].header.first_mb_in_slice >= total_mbs || + (i > 0 && slices[i].header.first_mb_in_slice <= + slices[i - 1].header.first_mb_in_slice)) + return -1; + } + + if (is_idr) + v->dpb_count = 0; + for (i = v->dpb_count - 1; i >= 0 && dpb_fill < MAX_DPB_FRAMES; i--) + { + int bi = v->dpb_order[i]; + dpb_y[dpb_fill] = PHYS(v->frame_y[bi]); + dpb_cb[dpb_fill] = PHYS(v->frame_cb[bi]); + dpb_cr[dpb_fill] = PHYS(v->frame_cr[bi]); + dpb_fill++; + } + + memset(UNCACHED(v->slice_desc), 0, SLICE_DESC_SIZE); + for (i = 0; i < slice_count; i++) + { + struct slice_hdr *sh = &slices[i].header; + int next_mb = i + 1 < slice_count ? + slices[i + 1].header.first_mb_in_slice : total_mbs; + int mb_count = next_mb - sh->first_mb_in_slice; + int slice_qp = 26 + v->pps.pic_init_qp_minus26 + + sh->slice_qp_delta; + int hdr_bytes = sh->bits_consumed / 8; + int bit_off = sh->bits_consumed & 7; + int ebsp_hdr_offset = map_rbsp_to_ebsp( + slices[i].nalu, slices[i].nalu_len, hdr_bytes); + int dma_len = slices[i].nalu_len - ebsp_hdr_offset; + int dma_offset = (dma_used + 31) & ~31; + + if (mb_count <= 0 || slice_qp < 0 || slice_qp > 51 || + dma_len <= 0 || dma_offset > BS_DMA_SIZE - dma_len) + return -1; + memcpy(UNCACHED(v->bs_dma + dma_offset), + slices[i].nalu + ebsp_hdr_offset, dma_len); + build_slice_descriptor( + (uint32_t *)UNCACHED(v->slice_desc) + + i * SLICE_DESC_WORDS, + sh->first_mb_in_slice, mb_count, sh->slice_type, slice_qp, + v->pps.chroma_qp_index_offset, v->pps.weighted_pred_flag, + sh->num_ref_idx_l0_active_minus1, + sh->disable_deblocking_filter_idc, + sh->alpha_c0_offset_div2, sh->beta_offset_div2, + bit_off, PHYS(v->bs_dma + dma_offset), dma_len); + dma_used = dma_offset + dma_len; + } + + ret = vpub_decode( + PHYS(v->ctrl_buf), PHYS(v->slice_desc), + PHYS(v->frame_y[out_buf]), PHYS(v->frame_cb[out_buf]), + PHYS(v->frame_cr[out_buf]), dpb_y, dpb_cb, dpb_cr, dpb_fill, + v->pic_wmb, v->pic_hmb, v->pic_w, slice_count); + commit_discard_dcache(); + if (ret != 0) + return -1; + + v->last_out = out_buf; + if (slices[0].nal_ref_idc != 0 && v->sps.max_num_ref_frames > 0) + { + int max_ref = MIN(v->sps.max_num_ref_frames, + MAX_DPB_FRAMES - 1); + while (v->dpb_count >= max_ref) + { + for (i = 0; i < v->dpb_count - 1; i++) + v->dpb_order[i] = v->dpb_order[i + 1]; + v->dpb_count--; + } + v->dpb_order[v->dpb_count++] = out_buf; + } + v->cur_out = (out_buf + 1) % MAX_DPB_FRAMES; + return 1; +} + +int vpu_h264_decode_nalu(struct vpu_h264 *v, + const uint8_t *nalu, int nalu_len) +{ + struct picture_slice slice; + struct bs b; + int rbsp_len; + int nal_type; + + if (!v || !nalu || nalu_len < 1 || (nalu[0] & 0x80) != 0) + return -1; + rbsp_len = ebsp_to_rbsp(v->nalu_buf, nalu, MIN(nalu_len, 256)); + nal_type = v->nalu_buf[0] & 0x1f; + b.buf = v->nalu_buf; + b.bit_offset = 0; + b.bit_length = rbsp_len * 8; + b.error = 0; + + if (nal_type == 7) + { + parse_sps(&v->sps, &b); + if (b.error || apply_sps_dimensions(v) < 0) + return -1; + v->have_sps = 1; + return 0; + } + if (nal_type == 8) + { + parse_pps(&v->pps, &b); + if (b.error || !pps_supported(&v->pps)) + return -1; + v->have_pps = 1; + return 0; + } + if (nal_type == 1 || nal_type == 5) + { + if (parse_picture_slice(v, nalu, nalu_len, &slice) < 0) + return -1; + return decode_picture(v, &slice, 1); + } + return 0; +} + +int vpu_h264_decode_sample(struct vpu_h264 *v, const uint8_t *sample, + int sample_len, int nalu_len_size) +{ + struct picture_slice slices[VPU_MAX_SLICES]; + int slice_count = 0; + int position = 0; + + if (!v || !sample || sample_len <= 0 || + nalu_len_size < 1 || nalu_len_size > 4) + return -1; + while (position + nalu_len_size <= sample_len) + { + uint32_t nalu_len = 0; + int nal_type; + int i; + + for (i = 0; i < nalu_len_size; i++) + nalu_len = (nalu_len << 8) | sample[position + i]; + position += nalu_len_size; + if (nalu_len == 0 || nalu_len > (uint32_t)(sample_len - position)) + return -1; + if ((sample[position] & 0x80) != 0) + return -1; + nal_type = sample[position] & 0x1f; + if (nal_type == 1 || nal_type == 5) + { + if (slice_count >= (int)VPU_MAX_SLICES || + parse_picture_slice(v, sample + position, nalu_len, + &slices[slice_count]) < 0) + return -1; + slice_count++; + } + else if (nal_type == 7 || nal_type == 8) + { + if (vpu_h264_decode_nalu(v, sample + position, nalu_len) < 0) + return -1; + } + position += nalu_len; + } + if (position != sample_len) + return -1; + return slice_count > 0 ? decode_picture(v, slices, slice_count) : 0; +} + +void vpu_h264_get_frame(const struct vpu_h264 *v, + const uint8_t **y, const uint8_t **cb, + const uint8_t **cr, int *w, int *h, int *stride) +{ + int luma_offset = v->sps.crop_top * v->pic_w + v->sps.crop_left; + int chroma_offset = (v->sps.crop_top / 2) * (v->pic_w / 2) + + v->sps.crop_left / 2; + + if (y) + *y = v->frame_y[v->last_out] + luma_offset; + if (cb) + *cb = v->frame_cb[v->last_out] + chroma_offset; + if (cr) + *cr = v->frame_cr[v->last_out] + chroma_offset; + if (w) + *w = v->display_w; + if (h) + *h = v->display_h; + if (stride) + *stride = v->pic_w; +} + +void vpu_h264_close(struct vpu_h264 *v) +{ + if (v) + vpub_power_off(); +} + +const struct hw_h264_api target_hw_h264_api = { + .buf_size = vpu_h264_buf_size, + .open = vpu_h264_open, + .configure = vpu_h264_configure, + .decode_sample = vpu_h264_decode_sample, + .get_frame = vpu_h264_get_frame, + .close = vpu_h264_close, +}; diff --git a/firmware/target/arm/s5l8702/ipod6g/vpu-h264-6g.h b/firmware/target/arm/s5l8702/ipod6g/vpu-h264-6g.h new file mode 100644 index 0000000000..b5f9b65ddf --- /dev/null +++ b/firmware/target/arm/s5l8702/ipod6g/vpu-h264-6g.h @@ -0,0 +1,84 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2025-2026 David Cormier + * + * S5L8702 VPU-B H.264 Baseline hardware decoder. + * + * Drives the VPU-B block on iPod Classic 6G (S5L8702). + * Full hardware decode: CAVLC, dequant, IDCT, intra/inter prediction, + * motion compensation, and in-loop deblocking. + * + * Supports: H.264 Baseline profile, level <= 3.0, up to ten ordered slices + * per frame. The single-slice path is proven bit-perfect against ffmpeg; + * multi-slice support follows the same Apple VPU descriptor interface. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#ifndef VPU_H264_6G_H +#define VPU_H264_6G_H + +#include "hw_h264.h" + +#include +#include + +/* Returns minimum buffer size needed for given max dimensions. + * Caller must provide a contiguous buffer of at least this size + * to vpu_h264_open(). Accounts for alignment padding. */ +size_t vpu_h264_buf_size(int max_w, int max_h); + +/* Initialize decoder. Allocates internal buffers from buf via bump + * allocator. Powers on VPU-B hardware and IRQ. + * Returns context pointer (inside buf) or NULL on error. */ +struct vpu_h264 *vpu_h264_open(void *buf, size_t buf_size, + int max_w, int max_h); + +/* Feed avcC decoder configuration (from MP4 container). + * Parses SPS and PPS NALUs from the avcC blob. + * Returns 0 on success, -1 on error. */ +int vpu_h264_configure(struct vpu_h264 *v, + const uint8_t *avcc, int avcc_len); + +/* Feed one raw NALU (including nal_header byte, excluding length prefix). + * Accepts EBSP (emulation prevention bytes intact). + * Handles SPS (7), PPS (8), IDR slice (5), non-IDR slice (1) internally. + * For slice NALUs: triggers synchronous HW decode (~5ms). + * + * Returns: 1 = frame decoded (call vpu_h264_get_frame) + * 0 = NALU consumed (SPS/PPS/SEI, no frame output) + * -1 = error (STATUS1 bits or timeout) */ +int vpu_h264_decode_nalu(struct vpu_h264 *v, + const uint8_t *nalu, int nalu_len); + +/* Decode one length-prefixed AVC sample as a complete picture. All slices + * are submitted together, matching the two-slice files produced by iTunes' + * "Create iPod or iPhone Version" action. */ +int vpu_h264_decode_sample(struct vpu_h264 *v, const uint8_t *sample, + int sample_len, int nalu_len_size); + +/* Get pointers to last decoded frame planes (YCbCr 4:2:0 planar). + * Pointers are cached aliases; cache is already invalidated. + * stride is the coded luma pitch; w/h are the cropped display dimensions. + * Valid until the next decode call. */ +void vpu_h264_get_frame(const struct vpu_h264 *v, + const uint8_t **y, const uint8_t **cb, + const uint8_t **cr, int *w, int *h, int *stride); + +/* Shutdown decoder. Powers off VPU-B hardware. */ +void vpu_h264_close(struct vpu_h264 *v); + +#endif /* VPU_H264_6G_H */ diff --git a/firmware/target/arm/s5l8702/ipodnano3g/TESTING.md b/firmware/target/arm/s5l8702/ipodnano3g/TESTING.md new file mode 100644 index 0000000000..bf26391dbc --- /dev/null +++ b/firmware/target/arm/s5l8702/ipodnano3g/TESTING.md @@ -0,0 +1,33 @@ +# iPod Nano 3G testing notes + +Evidence for the driver, FTL, storage and hardware support in this +directory. + +## NAND driver, FTL and storage + +Since the earlier host and hardware pass: `test_disk` passes at every +buffer alignment - it's what found and confirmed the fix for a DMA +alignment bug (a misaligned buffer returned wrong data and wrote past +itself). The storage thread holds the FTL for at most 492 ms during a +merge, measured with a timing build - within the 3 s PCM buffer, so no +audio starvation. + +The 8GB Hynix A555D5AD row's evidence is Robin Tschirschnitz's (delacor): +measured and run on their own unit, not this one. + +## NAND check image + +Tested on the 4GB unit from DFU: host sees "Nano 3G NAND", 2113537 x +2048-byte sectors, write protect on. `nandcheck.py collect` reads it in +seven minutes on Linux and Windows, no ECC failures or timeouts, and the +archive passes the host FTL suite. + +## Power, RTC, backlight, battery, audio + +Tested on the 4GB unit: 44.1 kHz WAV plays with elapsed time running; +battery reads 4077 mV, charging true/false correctly with a cell below +full; a time set in Rockbox survives a reboot; backlight and brightness +work; "pause on headphone unplug" pauses on disconnect. + +Not tested: sample rates other than 44.1 kHz, and the charge-complete +status bits (never seen set). diff --git a/firmware/target/arm/s5l8702/ipodnano3g/adc-nano3g.c b/firmware/target/arm/s5l8702/ipodnano3g/adc-nano3g.c index c1e63e76f0..794dc07aa1 100644 --- a/firmware/target/arm/s5l8702/ipodnano3g/adc-nano3g.c +++ b/firmware/target/arm/s5l8702/ipodnano3g/adc-nano3g.c @@ -27,17 +27,51 @@ #include "pmu-target.h" #include "kernel.h" +/* Only the battery input is known: the original firmware selects it with + * 0x24 and converts with 2500 mV + raw * 2000 mV / 1023 (measured: 4062 mV + * on battery, 4110 mV charging). The USB data and accessory inputs are not + * identified yet and read as 0. */ +static const struct pmu_adc_channel adc_channels[] = +{ + [ADC_BATTERY] = + { + .name = "Battery", + .mux = 0x24, + .samples = 4, + .offset_mv = 2500, + .span_mv = 2000, + }, + [ADC_USBDATA] = + { + .name = "USB data", + }, + [ADC_ACCESSORY] = + { + .name = "Accessory", + }, +}; + +unsigned short adc_read_millivolts(int channel) +{ + const struct pmu_adc_channel *ch = &adc_channels[channel]; + + if (!ch->samples) + return 0; + return pmu_adc_raw2mv(ch, pmu_read_adc(ch)); +} + /* Returns battery voltage [millivolts] */ unsigned int adc_read_battery_voltage(void) { - return 0; + return adc_read_millivolts(ADC_BATTERY); } /* API functions */ unsigned short adc_read(int channel) { - (void) channel; - return 0; + const struct pmu_adc_channel *ch = &adc_channels[channel]; + + return ch->samples ? pmu_read_adc(ch) : 0; } int adc_read_accessory_resistor(void) @@ -53,8 +87,7 @@ unsigned int adc_read_usbdata_voltage(bool dp) const char *adc_name(int channel) { - (void) channel; - return ""; + return adc_channels[channel].name; } void adc_init(void) diff --git a/firmware/target/arm/s5l8702/ipodnano3g/backlight-nano3g.c b/firmware/target/arm/s5l8702/ipodnano3g/backlight-nano3g.c index 34fc198471..5b557aabd5 100644 --- a/firmware/target/arm/s5l8702/ipodnano3g/backlight-nano3g.c +++ b/firmware/target/arm/s5l8702/ipodnano3g/backlight-nano3g.c @@ -30,7 +30,7 @@ #include "lcd-s5l8702.h" #endif -// TODO: test +/* The original firmware writes the level to bits 0..4 as well */ void backlight_hw_brightness(int brightness) { pmu_write(D1671_REG_LEDCTL, @@ -43,8 +43,11 @@ void backlight_hw_on(void) if (!lcd_active()) lcd_awake(); #endif + /* As the original firmware turns it on: bits 7 and 6 set, + * bit 5 clear. Bit 6's meaning is unknown. */ pmu_write(D1671_REG_LEDCTL, - (pmu_read(D1671_REG_LEDCTL) | D1671_LEDCTL_ENABLE)); + (pmu_read(D1671_REG_LEDCTL) & ~0x20) + | D1671_LEDCTL_ENABLE | D1671_LEDCTL_UNKNOWN); } void backlight_hw_off(void) diff --git a/firmware/target/arm/s5l8702/ipodnano3g/ftl-nano3g.c b/firmware/target/arm/s5l8702/ipodnano3g/ftl-nano3g.c new file mode 100644 index 0000000000..393aa60324 --- /dev/null +++ b/firmware/target/arm/s5l8702/ipodnano3g/ftl-nano3g.c @@ -0,0 +1,2692 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2009 by Michael Sparmann + * + * The log-block handling, the control-block list and the VFL context commit + * follow firmware/target/arm/s5l8700/ipodnano2g/ftl-nano2g.c, which decodes + * the same Whimory structures on the Nano 2G. Several functions are ports of + * their counterparts there. + * + * Copyright (C) 2026 by Andrew Rice + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +/* + * FTL for the iPod Nano 3G: Apple's Whimory, in the variant this firmware + * uses. It is related to the Nano 2G's (ftl-nano2g.c), but the VFL context + * layout differs and a virtual block groups one to four physical blocks per + * chip enable, as the chip's mode lays them out. + * + * + * From the bottom up: + * - VFL: each bank's blocks form planes units ("planes"); virtual block b + * of a unit is a physical block placed by the chip's mode + * (ftl_unit_block()), unless that bank's VFL context remaps it. The + * context holds one table of vflspares slots per unit. Slot k holds the + * physical block it replaces (0xfff0 free, 0xffff unusable) and stands + * for the reserved virtual block nsuperblocks + k % vflspares of unit + * k / vflspares, as Apple's VFL numbers them. + * - Superblocks: superblock s is virtual block s of every unit of every + * bank. Its pages are numbered v = page * banks * planes + plane * banks + * + bank. + * - FTL: logical page lpn is at index lpn % sbpages of logical block + * lpn / sbpages. The block map, committed in the FTL control blocks, says + * which superblock holds each logical block. Pages written since a block + * was last merged are in log superblocks, which the context and its log + * offset tables record at every commit; a page present in a log is newer + * than the one the map points at, and ftl_resolve() prefers it. + * + * The write path follows the original firmware's own FTL: writes append to + * logs, a merge folds or compacts them when they fill or their slot is + * needed, and ftl_sync() folds every log and commits, as the firmware's + * flush does. Nothing else commits, so a mount that finds writes after the + * last commit rebuilds the tables from the medium, as the firmware's + * restore does. Pages go to the flash in runs, the way the firmware hands + * them to the VFL. + * A block that will not take a write or an erase is replaced from the VFL's + * reserved spares. + */ + +#include "config.h" +#include "system.h" +#include "kernel.h" +#include "logf.h" +#include "panic.h" +#include "storage.h" +#include "nand-target.h" +#include "ftl-target.h" +#include +#include +#include + +#define FTL_MAX_BANKS 4 +/* Sized for the largest chip in nand_chip_table[]: the 8GB parts give 3895 + * superblocks and 3872 logical blocks. The 4GB one needs half of that. */ +#define FTL_MAX_SB 4096 /* superblocks in the virtual area */ +#define FTL_MAX_USERSB 4096 /* logical blocks */ +#define FTL_MAX_SBPAGES 1024 /* pages per superblock */ +#define FTL_MAX_LOGS 17 /* Whimory keeps 0x11 log blocks */ +#define FTL_MAP_PAGES (FTL_MAX_USERSB * 2 / NAND_PAGE_SIZE) +#define FTL_VFL_SCANBLOCKS 200 /* where to look for a VFL context */ +#define FTL_NO_PAGE 0xffff +#define FTL_VFL_FREE 0xfff0 /* a reserved block nothing uses yet */ +#define FTL_VFL_BAD 0xffff /* a reserved block that is itself bad */ +#define FTL_VFL_PENDING 20 /* Apple's pending-bad list length */ + +/* Spare metadata types */ +#define SPARE_DATA 0x40 +#define SPARE_DATA_LAST 0x41 /* last page of a superblock */ +#define SPARE_FTL_CXT 0x43 +#define SPARE_FTL_MAP 0x44 +#define SPARE_FTL_LOG 0x45 /* log page-offset table */ +#define SPARE_FTL_ERASECTR 0x46 +#define SPARE_FTL_STATS 0x47 +#define SPARE_FTL_READCOUNT 0x49 +#define SPARE_FTL_MOUNT 0x4f /* "written to since the last commit" */ +#define SPARE_VFL_CXT 0x80 +#define SPARE_ERASED 0xff + +/* The FTL context, as it sits in its flash page. The named fields are the + * ones we understand; the pad arrays keep everything else exactly as we + * found it, so a commit of ours preserves what the original firmware + * expects. Field offsets were derived from the firmware's own context load + * and store, and the FTL_CHECK assertions below pin every one of them. */ +#define FTL_POOL_SIZE 20 /* spare superblocks: free pool + logs */ +#define FTL_CXT_LOGS 18 /* log slots in the context */ +#define FTL_CXT_PAGELIST 36 /* entries in the side-table page lists */ + +struct ftl_cxt_log +{ + uint32_t usn; + uint16_t sb; + uint16_t lblock; + uint32_t offsetsptr; /* Whimory's RAM pointer, preserved unused */ + uint16_t pagesused; + uint16_t pagescurrent; + uint32_t issequential; +}; + +struct ftl_cxt +{ + uint32_t usn; /* decrements once per control page */ + uint32_t maxusn; /* highest user-data usn */ + uint16_t freecount; /* live entries in blockpool */ + uint16_t nextfreeidx; /* head of the free ring */ + uint16_t swapcounter; /* wear-levelling budget: +1 per merge */ + uint16_t blockpool[FTL_POOL_SIZE]; + uint8_t field_36[2]; + uint32_t mappages[18]; /* vpages holding the block map */ + uint32_t ecpages[FTL_CXT_PAGELIST]; /* ... the erase counters */ + uint32_t logpages[FTL_MAX_LOGS]; /* ... the log offset tables */ + uint8_t field_154[0x44]; + uint32_t mapptr; /* three more of Whimory's RAM pointers */ + uint32_t ecptr; + uint32_t logoffsptr; + struct ftl_cxt_log logs[FTL_CXT_LOGS]; + uint8_t field_30c[6]; + uint16_t ctrlblocks[3]; + uint32_t ctrlpage; + uint32_t cleanflag; /* set by a commit, cleared by a write */ + uint32_t rcpages[FTL_CXT_PAGELIST]; /* ... the read counters */ + uint32_t rcptr; + uint8_t field_3b4[0x1c]; + uint32_t statspage; + uint32_t statsflag; /* 0xffffffff = statspage is set */ + uint8_t field_3d8[0x428]; +}; + +#define FTL_SIDE_ENTRIES (FTL_MAX_SB + 32) /* per-superblock tables */ +#define FTL_SIDE_PAGES ((FTL_SIDE_ENTRIES * 2 + NAND_PAGE_SIZE - 1) \ + / NAND_PAGE_SIZE) +#define FTL_LOG_PAGES ((FTL_MAX_SBPAGES * FTL_MAX_LOGS * 2 \ + + NAND_PAGE_SIZE - 1) / NAND_PAGE_SIZE) +/* The page counts above are at the smallest page size. A table is read and + * written a whole page at a time, so its buffer is rounded up to the + * largest. */ +#define FTL_WHOLE_PAGES(bytes) (((bytes) + NAND_MAX_PAGE_SIZE - 1) \ + / NAND_MAX_PAGE_SIZE * NAND_MAX_PAGE_SIZE) +#define FTL_SIDE_BYTES FTL_WHOLE_PAGES(FTL_SIDE_ENTRIES * 2) +#define FTL_LOG_BYTES FTL_WHOLE_PAGES(FTL_MAX_SBPAGES * FTL_MAX_LOGS * 2) +/* Pages one commit occupies: erase counters, read counters, map, log + * tables, stats, then the context itself */ +#define FTL_COMMIT_PAGES(ec, rc, map, log) ((ec) + (rc) + (map) + (log) + 2) + +/* VFL context, one per bank: nano2g's header, then per-plane remap tables */ +struct ftl_vfl_cxt +{ + uint32_t usn; /* cross-bank update sequence number */ + uint16_t ftlctrlblocks[3]; /* superblocks holding FTL control pages */ + uint16_t field_a; + uint32_t updatecount; /* decrementing, also in the spare */ + uint16_t activecxtblock; + uint16_t nextcxtpage; + uint16_t badcount; /* slots marked 0xffff, i.e. spares that + * are themselves bad - not a count of + * remapped blocks (measured) */ + uint16_t field_16[3]; + uint16_t usedcount[4]; /* slots taken in each unit's table */ + uint16_t field_24[4]; + uint16_t remap[820]; /* plane tables, vflspares entries each */ + uint16_t vflcxtblocks[4]; /* physical blocks of the context ring */ + uint16_t pendingcount; /* blocks awaiting replacement */ + uint16_t pending[FTL_VFL_PENDING]; /* physical blocks that failed a read + * or write, replaced at their next + * erase */ + uint8_t field_6c6[0x12e]; + uint32_t version; + uint32_t checksum1; + uint32_t checksum2; +}; + +#define FTL_CHECK(name, cond) typedef char name[(cond) ? 1 : -1] +FTL_CHECK(ftl_vfl_cxt_size, sizeof(struct ftl_vfl_cxt) == 0x800); +FTL_CHECK(ftl_vfl_cxt_remap, offsetof(struct ftl_vfl_cxt, remap) == 0x2c); +FTL_CHECK(ftl_vfl_cxt_blocks, + offsetof(struct ftl_vfl_cxt, vflcxtblocks) == 0x694); +FTL_CHECK(ftl_vfl_cxt_pending, + offsetof(struct ftl_vfl_cxt, pending) == 0x69e); +FTL_CHECK(ftl_vfl_cxt_cks, offsetof(struct ftl_vfl_cxt, checksum1) == 0x7f8); +FTL_CHECK(ftl_cxt_size, sizeof(struct ftl_cxt) == 0x800); +FTL_CHECK(ftl_cxt_log_size, sizeof(struct ftl_cxt_log) == 20); +FTL_CHECK(ftl_cxt_pool, offsetof(struct ftl_cxt, blockpool) == 0x00e); +FTL_CHECK(ftl_cxt_map, offsetof(struct ftl_cxt, mappages) == 0x038); +FTL_CHECK(ftl_cxt_ec, offsetof(struct ftl_cxt, ecpages) == 0x080); +FTL_CHECK(ftl_cxt_logp, offsetof(struct ftl_cxt, logpages) == 0x110); +FTL_CHECK(ftl_cxt_logs, offsetof(struct ftl_cxt, logs) == 0x1a4); +FTL_CHECK(ftl_cxt_ctrl, offsetof(struct ftl_cxt, ctrlblocks) == 0x312); +FTL_CHECK(ftl_cxt_clean, offsetof(struct ftl_cxt, cleanflag) == 0x31c); +FTL_CHECK(ftl_cxt_rc, offsetof(struct ftl_cxt, rcpages) == 0x320); +FTL_CHECK(ftl_cxt_stats, offsetof(struct ftl_cxt, statspage) == 0x3d0); + +/* A log block: pages of one logical block written since the last commit, + * scattered through a superblock of their own. Fields follow + * ftl-nano2g.c's struct ftl_log_type, which is the same structure Apple + * keeps at context offset 0x1a4. */ +struct ftl_log +{ + uint32_t usn; + uint16_t sb; /* the scattered superblock */ + uint16_t lblock; /* the logical block it stands for */ + uint16_t pagesused; /* append cursor */ + uint16_t pagescurrent; /* pages still live, i.e. to copy */ + uint32_t issequential; /* still written in order? */ + uint16_t offsets[FTL_MAX_SBPAGES]; /* index in lblock -> v in sb */ +}; + +/* Source of replacement page data for ftl_write_merge(): a pointer to a + * page of data for lpn, or NULL to keep what is there now. */ +typedef const void *(*ftl_write_src)(uint32_t lpn, void *ctx); + +static const struct nand_geometry *geo; +static uint32_t nsuperblocks; /* superblocks in the virtual area */ +static uint32_t sbpages; /* pages per superblock */ +static uint32_t usersb; /* logical blocks */ +static uint32_t pagesize; /* bytes per NAND page */ +static uint32_t secperpage; /* NAND_PAGE_SIZE sectors in one page */ +static bool ftl_mounted = false; +static struct mutex ftl_mtx; + +static struct ftl_vfl_cxt ftl_vfl_cxt[FTL_MAX_BANKS]; +static uint16_t ftl_map[FTL_MAX_USERSB]; +static uint16_t ftl_ctrl[3]; +static struct ftl_log ftl_logs[FTL_MAX_LOGS]; +/* A page-sized scratch for the log offset tables, which a clean load reads + * and a commit writes - never both at once, so they share it */ +static uint16_t ftl_logtable[FTL_LOG_BYTES / 2] STORAGE_ALIGN_ATTR; + +/* + * The buffers here and below are sized for the largest chip in the driver's + * table, since storage is up long before there is an allocator to ask. They + * come to about 540KB, 2% of this target's 28MB audio buffer. Overlapping + * the mount-time ones (ftl_rs, the restore's log slots) with ftl_copybuf + * would save 68KB, and halving the copy batch another 128KB, but the first + * trades a user's flash against an aliasing invariant and the second slows + * every merge; 0.4% of the buffer does not pay for either. ftl_copybuf's + * size is one constant (FTL_COPY_PAGES) for anyone who disagrees. + */ +static unsigned int ftl_nlogs; + +static uint8_t ftl_buffer[NAND_MAX_PAGE_SIZE] STORAGE_ALIGN_ATTR; +static uint32_t ftl_meta[NAND_META_WORDS]; + +/* Write-path state. The committed context page is kept verbatim so that a + * commit of ours preserves every field we do not understand. */ +static bool ftl_writable = false; +static bool ftl_readonly = false; /* not writable since the mount */ +static struct ftl_cxt ftl_cxt STORAGE_ALIGN_ATTR; +static uint16_t ftl_erasectr[FTL_SIDE_BYTES / 2]; +static uint16_t ftl_readcount[FTL_SIDE_BYTES / 2]; +static uint8_t ftl_stats[NAND_MAX_PAGE_SIZE] STORAGE_ALIGN_ATTR; +/* A 0x800-byte context padded out to a whole page for writing */ +static uint8_t ftl_cxtpage[NAND_MAX_PAGE_SIZE] STORAGE_ALIGN_ATTR; +/* A page holding sectors a read or write covers only part of */ +static uint8_t ftl_sectorpage[NAND_MAX_PAGE_SIZE] STORAGE_ALIGN_ATTR; +static uint32_t ftl_vfl_usn; /* cross-bank VFL context update counter */ +static uint32_t ftl_ctrl_idx; /* which of ftl_ctrl[] is being written */ +static uint32_t ftl_ctrl_v; /* next free page in it */ +static uint32_t ftl_necpages, ftl_nrcpages, ftl_nmappages, ftl_nlogpages; + +/* The run of pages being gathered for one program call (see ftl_run_add) */ +static struct nand_write ftl_run[FTL_MAX_SBPAGES]; +static uint32_t ftl_run_meta[FTL_MAX_SBPAGES][NAND_META_WORDS]; +static uint32_t ftl_run_sb, ftl_run_v, ftl_run_n; +/* Merges read a batch of pages, then write it: an eighth of a superblock + * at a time, as the firmware's simple, copy and move merges do. + * The buffer holds that at any page size, since ftl_init() refuses a + * superblock larger than FTL_MAX_SBPAGES pages of NAND_PAGE_SIZE. */ +#define FTL_COPY_PAGES (FTL_MAX_SBPAGES / 8) +static uint8_t ftl_copybuf[FTL_COPY_PAGES * NAND_PAGE_SIZE] STORAGE_ALIGN_ATTR; +static struct nand_read ftl_read_run[FTL_MAX_SBPAGES]; +static uint32_t ftl_read_meta[FTL_MAX_SBPAGES][NAND_META_WORDS]; + +static uint32_t meta_type(const uint32_t *meta) +{ + return (meta[2] >> 8) & 0xff; +} + +static bool meta_is_data(const uint32_t *meta) +{ + return meta_type(meta) == SPARE_DATA || meta_type(meta) == SPARE_DATA_LAST; +} + +/* Read a page into ftl_buffer and ftl_meta. Returns its spare type, or -1 */ +static int ftl_raw_read(uint32_t bank, uint32_t page) +{ + int rc = nand_read_page(bank, page, ftl_buffer, ftl_meta); + if (rc < 0 || rc == NAND_ECC_FAILED) + return -1; + return meta_type(ftl_meta); +} + +static void ftl_vfl_calculate_checksum(const struct ftl_vfl_cxt *cxt, + uint32_t *sum, uint32_t *x) +{ + const uint32_t *w = (const uint32_t *)cxt; + unsigned int i; + + *sum = 0xAABBCCDD; + *x = 0xAABBCCDD; + for (i = 0; i < offsetof(struct ftl_vfl_cxt, checksum1) / 4; i++) + { + *sum += w[i]; + *x ^= w[i]; + } +} + +static bool ftl_vfl_checksum_ok(const struct ftl_vfl_cxt *cxt) +{ + uint32_t sum, x; + + ftl_vfl_calculate_checksum(cxt, &sum, &x); + return sum == cxt->checksum1 && x == cxt->checksum2; +} + +/* A VFL context commit is 8 copies. Load the first good one at or after + * page into ftl_buffer. */ +static int ftl_vfl_read_cxt(uint32_t bank, uint32_t block, uint32_t page) +{ + uint32_t i; + + for (i = page; i < page + 8 && i < geo->pagesperblock; i++) + if (ftl_raw_read(bank, block * geo->pagesperblock + i) + == SPARE_VFL_CXT + && (ftl_meta[2] & 0xff) == 0 + && ftl_vfl_checksum_ok((const struct ftl_vfl_cxt *)ftl_buffer)) + return 0; + return -1; +} + +/* Find the newest VFL context of a bank, as nano2g's ftl_vfl_open() does */ +static int ftl_vfl_open_bank(uint32_t bank) +{ + uint16_t ring[4]; + uint32_t i, block, last, best = 4, bestusn = 0xffffffff; + + /* Any context lists the blocks the context ring lives in */ + for (block = 1; block < FTL_VFL_SCANBLOCKS; block++) + if (!ftl_vfl_read_cxt(bank, block, 0)) + break; + if (block == FTL_VFL_SCANBLOCKS) + return -1; + memcpy(ring, ((const struct ftl_vfl_cxt *)ftl_buffer)->vflcxtblocks, + sizeof(ring)); + + /* The ring's newest block has the lowest update count */ + for (i = 0; i < 4; i++) + { + if (ring[i] >= geo->blocks || ftl_vfl_read_cxt(bank, ring[i], 0)) + continue; + if (ftl_meta[0] && ftl_meta[0] <= bestusn) + { + bestusn = ftl_meta[0]; + best = i; + } + } + if (best == 4) + return -1; + + /* Its last commit is the newest context */ + block = ring[best]; + last = 0; + for (i = 8; i < geo->pagesperblock; i += 8) + { + if (ftl_vfl_read_cxt(bank, block, i)) + break; + last = i; + } + if (ftl_vfl_read_cxt(bank, block, last)) + return -1; + memcpy(&ftl_vfl_cxt[bank], ftl_buffer, sizeof(struct ftl_vfl_cxt)); + return 0; +} + +/* The physical block of virtual block vblock of a bank's unit, as the + * original firmware's per-mode address functions place it */ +static uint32_t ftl_unit_block(uint32_t unit, uint32_t vblock) +{ + uint32_t half = geo->blocks / 2; + + switch (geo->layout) + { + case NAND_LAYOUT_SINGLE: + return vblock; + case NAND_LAYOUT_HALVES: + return vblock + unit * half; + case NAND_LAYOUT_BOTH: + return 2 * vblock + (unit & 1) + ((unit & 2) ? half : 0); + case NAND_LAYOUT_SPLIT13: + /* 8320 blocks: two 4096-block regions halved, then 128 blocks in + * four runs of 32 */ + if (vblock < 4096) + return vblock + (vblock >= 2048 ? 2048 : 0) + unit * 2048; + if (vblock < 4128) + return 8192 + unit * 32 + (vblock - 4096); + return 8256 + unit * 32 + (vblock - 4128); + default: /* NAND_LAYOUT_ADJACENT */ + return 2 * vblock + unit; + } +} + +/* The physical block of slot k's reserved block */ +static uint32_t ftl_vfl_spare_block(uint32_t k) +{ + return ftl_unit_block(k / geo->vflspares, + nsuperblocks + k % geo->vflspares); +} + +/* Check that a bank's VFL context is laid out as the chip table row says. + * Each unit's table is vflspares slots: the physical blocks it has + * replaced and bad spares, then from its first free slot only free and bad + * ones, and the rest of the array is zero (measured on the 4GB unit). A + * wrong reserved count moves those boundaries, so this refuses a row that + * does not describe the chip rather than mount it with the wrong map. */ +static int ftl_vfl_check_cxt(const struct ftl_vfl_cxt *cxt) +{ + uint32_t unit, i, v; + bool freeseen; + + for (i = 0; i < 4; i++) + if (cxt->vflcxtblocks[i] >= geo->blocks) + return -1; + for (i = 0; i < 3; i++) + if (cxt->ftlctrlblocks[i] >= nsuperblocks) + return -1; + for (unit = 0; unit < geo->planes; unit++) + { + const uint16_t *table = &cxt->remap[unit * geo->vflspares]; + + if (cxt->usedcount[unit] > geo->vflspares) + return -1; + freeseen = false; + for (i = 0; i < geo->vflspares; i++) + { + v = table[i]; + if (v == FTL_VFL_FREE) + freeseen = true; + else if (v != FTL_VFL_BAD && (freeseen || v >= geo->blocks)) + return -1; + } + } + for (i = geo->planes * geo->vflspares; i < ARRAYLEN(cxt->remap); i++) + if (cxt->remap[i]) + return -1; + return 0; +} + +/* Where virtual block vblock of a unit really is: its own physical block, + * or the reserved block a slot of any unit replaced it with - a unit whose + * table is full borrows another's, so Apple's lookup scans them all */ +static uint32_t ftl_vfl_phys_block(uint32_t bank, uint32_t unit, + uint32_t vblock) +{ + const uint16_t *remap = ftl_vfl_cxt[bank].remap; + uint32_t block = ftl_unit_block(unit, vblock); + uint32_t k; + + for (k = 0; k < geo->planes * geo->vflspares; k++) + if (remap[k] == block) + return ftl_vfl_spare_block(k); + return block; +} + +static void ftl_vpage_phys(uint32_t sb, uint32_t v, + uint32_t *bank, uint32_t *page) +{ + uint32_t plane = (v / geo->banks) % geo->planes; + + *bank = v % geo->banks; + *page = ftl_vfl_phys_block(*bank, plane, sb) * geo->pagesperblock + + v / (geo->banks * geo->planes); +} + +/* Read page v of superblock sb into ftl_buffer and ftl_meta */ +static int ftl_read_vpage(uint32_t sb, uint32_t v) +{ + uint32_t bank, page; + + ftl_vpage_phys(sb, v, &bank, &page); + return ftl_raw_read(bank, page); +} + +static uint32_t ftl_vpn(uint32_t sb, uint32_t v) +{ + return sb * sbpages + v; +} + +/* The spare types that live in FTL control blocks */ +static bool type_is_ctrl(int type) +{ + return type >= SPARE_FTL_CXT && type <= SPARE_FTL_MOUNT; +} + +/* Find the FTL context, as the firmware's context load does: the newest control + * block is the one whose page 0 has the lowest usn, and its last written + * page must be a context. Returns 1 if it is - the context then describes + * the medium exactly, open logs included - 0 if not, in which case the + * medium has changed since that commit and ftl_restore() must rebuild it, + * or a negative number if no context exists at all. Either way ftl_cxt + * holds the newest context page found, so a commit of ours preserves the + * fields we do not understand. */ +static int ftl_load_cxt(void) +{ + const struct ftl_vfl_cxt *newest = &ftl_vfl_cxt[0]; + uint32_t i, v, ctrl = 3, cxtv = FTL_MAX_SBPAGES; + uint32_t minusn = 0xffffffff, page0usn = 0xffffffff, newblk = 3; + uint32_t used[3] = { 0, 0, 0 }; + int type, lasttype[3] = { -1, -1, -1 }; + bool clean; + + for (i = 1; i < geo->banks; i++) + if (ftl_vfl_cxt[i].usn > newest->usn) + newest = &ftl_vfl_cxt[i]; + memcpy(ftl_ctrl, newest->ftlctrlblocks, sizeof(ftl_ctrl)); + + for (i = 0; i < 3; i++) + { + if (ftl_ctrl[i] >= nsuperblocks) + continue; + for (v = 0; v < sbpages; v++) + { + type = ftl_read_vpage(ftl_ctrl[i], v); + if (type == SPARE_ERASED) + break; + if (v == 0 && type_is_ctrl(type) && ftl_meta[0] < page0usn) + { + page0usn = ftl_meta[0]; + newblk = i; + } + if (type == SPARE_FTL_CXT && ftl_meta[0] <= minusn) + { + minusn = ftl_meta[0]; + ctrl = i; + cxtv = v; + } + lasttype[i] = type; + } + used[i] = v; + } + /* Clean: the newest block ends in a context. Otherwise keep the + * newest context anywhere, for its fields. */ + clean = newblk < 3 && lasttype[newblk] == SPARE_FTL_CXT; + if (clean) + { + ctrl = newblk; + cxtv = used[newblk] - 1; + } + if (ctrl == 3 || ftl_read_vpage(ftl_ctrl[ctrl], cxtv) != SPARE_FTL_CXT) + return -2; + memcpy(&ftl_cxt, ftl_buffer, sizeof(ftl_cxt)); + ftl_cxt.usn = ftl_meta[0]; + ftl_ctrl_idx = ctrl; + ftl_ctrl_v = used[ctrl]; + return clean; +} + +/* Read npages pages of a context pointer list into table, entries bytes */ +static int ftl_read_list(const uint32_t *list, uint32_t npages, int type, + void *table, uint32_t bytes) +{ + uint32_t i, vpn; + + for (i = 0; i < npages; i++) + { + vpn = list[i]; + if (vpn / sbpages >= nsuperblocks + || ftl_read_vpage(vpn / sbpages, vpn % sbpages) != type + || (ftl_meta[1] & 0xffff) != i) + return -1; + memcpy((uint8_t *)table + i * pagesize, ftl_buffer, + MIN(bytes - i * pagesize, pagesize)); + } + return 0; +} + +/* The rest of a clean load: map, open logs and free pool, all as the + * context records them */ +static int ftl_load_clean(void) +{ + uint32_t i, sb; + + if (ftl_read_list(ftl_cxt.mappages, + (usersb * 2 + pagesize - 1) / pagesize, + SPARE_FTL_MAP, + ftl_map, usersb * 2)) + return -3; + for (i = 0; i < usersb; i++) + if (ftl_map[i] >= nsuperblocks) + return -4; + + /* One offset table of sbpages entries per log slot, in slot order */ + if (ftl_read_list(ftl_cxt.logpages, + (sbpages * FTL_MAX_LOGS * 2 + pagesize - 1) + / pagesize, + SPARE_FTL_LOG, ftl_logtable, sbpages * FTL_MAX_LOGS * 2)) + return -5; + ftl_nlogs = 0; + for (i = 0; i < FTL_MAX_LOGS; i++) + { + const struct ftl_cxt_log *c = &ftl_cxt.logs[i]; + const uint16_t *offsets = ftl_logtable + i * sbpages; + struct ftl_log *log; + uint32_t idx; + + if (c->sb == 0xffff) + continue; + if (c->sb >= nsuperblocks || c->lblock >= usersb + || c->pagesused > sbpages || c->pagescurrent > sbpages) + return -6; + for (idx = 0; idx < sbpages; idx++) + if (offsets[idx] != FTL_NO_PAGE && offsets[idx] >= sbpages) + return -6; + log = &ftl_logs[ftl_nlogs++]; + log->usn = c->usn; + log->sb = c->sb; + log->lblock = c->lblock; + log->pagesused = c->pagesused; + log->pagescurrent = c->pagescurrent; + log->issequential = c->issequential; + memcpy(log->offsets, offsets, sbpages * 2); + } + + if (ftl_cxt.freecount > FTL_POOL_SIZE + || ftl_cxt.nextfreeidx >= FTL_POOL_SIZE) + return -7; + for (i = 0; i < ftl_cxt.freecount; i++) + { + sb = ftl_cxt.blockpool[(ftl_cxt.nextfreeidx + i) % FTL_POOL_SIZE]; + if (sb >= nsuperblocks) + return -7; + } + return 0; +} + +/* ---- Rebuilding the tables from the medium --------------------------- */ +/* Rebuilds the map, the open logs and the free pool from the medium alone, + * when the newest control page is not a context. Every phase follows the + * decode of the original firmware's restore; the letters are that decode's + * phases, and each one is a ftl_restore_*() of its own below, called in + * order by ftl_restore(). The letter is on the call and on the function, so + * the correspondence with the decode notes stays readable. Nothing is + * written here: the caller erases the hole blocks and the pool and commits + * (phases D, H and J) when the mount is writable. */ + +#define RS_LOG 0x40 +#define RS_DATA 0x41 +#define RS_CTRL 0x42 +#define RS_FREE 0x48 + +struct ftl_rs +{ + uint16_t lblock; + uint8_t state; + uint32_t usn; +}; +static struct ftl_rs ftl_rs[FTL_MAX_SB]; +static uint8_t ftl_hole[FTL_MAX_USERSB / 8 + 1]; /* map given an unerased + * free block */ +static uint32_t ftl_nholes; + +static bool ftl_is_hole(uint32_t lblock) +{ + return ftl_hole[lblock / 8] & (1 << (lblock % 8)); +} + +/* The firmware's promotion step: a log whose every readable page sits at + * its own index can stand as the data block */ +static bool ftl_log_in_place(uint32_t sb) +{ + uint32_t v; + + int type; + + for (v = 0; v < sbpages; v++) + { + type = ftl_read_vpage(sb, v); + if (type >= 0 && type != SPARE_ERASED && ftl_meta[0] % sbpages != v) + return false; + } + return true; +} + +/* the firmware's log scan: every page of the block, the last copy of each + * index winning */ +static void ftl_scan_log(struct ftl_log *log) +{ + uint32_t v, idx; + int type; + + memset(log->offsets, 0xff, sbpages * sizeof(log->offsets[0])); + log->pagescurrent = 0; + for (v = 0; v < sbpages; v++) + { + type = ftl_read_vpage(log->sb, v); + if (type < 0 || type == SPARE_ERASED) + continue; + idx = ftl_meta[0] % sbpages; + if (log->offsets[idx] == FTL_NO_PAGE) + log->pagescurrent++; + log->offsets[idx] = v; + } +} + +/* Phase C hands one slot to each log it keeps, and phase E drains the slots + * into ftl_logs[]. The array is static because struct ftl_log is about 2KB + * and eighteen of them will not fit on the mount's stack; it is live only + * between ftl_restore_place_logs() and ftl_restore_build_pool(). */ +#define FTL_RS_SLOTS (FTL_MAX_LOGS + 1) +static struct ftl_log ftl_rs_slots[FTL_RS_SLOTS]; + +/* phase A: the control blocks; the newest by page 0 holds the next commit's + * predecessor, and the next commit starts a fresh block */ +static int ftl_restore_ctrl_blocks(void) +{ + uint32_t i, page0usn = 0xffffffff; + + for (i = 0; i < 3; i++) + { + if (ftl_ctrl[i] >= nsuperblocks) + return -30; + ftl_rs[ftl_ctrl[i]].state = RS_CTRL; + if (ftl_read_vpage(ftl_ctrl[i], 0) >= 0 && ftl_meta[0] < page0usn) + { + page0usn = ftl_meta[0]; + ftl_ctrl_idx = i; + } + } + ftl_cxt.usn = page0usn; + ftl_ctrl_v = sbpages; + return 0; +} + +/* phase B: classify every other superblock by its last page, or by page 0 + * when that is erased or unreadable; closed blocks go into the map, the + * higher usn winning. Returns the highest user-data usn, which phase C + * needs and which the context records. */ +static uint32_t ftl_restore_classify(void) +{ + uint32_t sb, lblock, maxusn = 0; + int type; + + memset(ftl_map, 0xff, sizeof(ftl_map)); + for (sb = 0; sb < nsuperblocks; sb++) + { + struct ftl_rs *r = &ftl_rs[sb]; + + if (r->state == RS_CTRL) + continue; + r->state = RS_FREE; + type = ftl_read_vpage(sb, sbpages - 1); + if (type < 0 || type == SPARE_ERASED) + { + type = ftl_read_vpage(sb, 0); + if (type != SPARE_DATA) + continue; + r->state = RS_LOG; + } + else if (type == SPARE_DATA_LAST) + r->state = RS_DATA; + else if (type == SPARE_DATA) + r->state = RS_LOG; + else + continue; + lblock = ftl_meta[0] / sbpages; + if (lblock >= usersb) + { + r->state = RS_FREE; + continue; + } + r->lblock = lblock; + r->usn = ftl_meta[1]; + if (r->usn > maxusn) + maxusn = r->usn; + if (r->state != RS_DATA) + continue; + if (ftl_map[lblock] != 0xffff) + { + if (ftl_rs[ftl_map[lblock]].usn >= r->usn) + { + r->state = RS_FREE; + continue; + } + ftl_rs[ftl_map[lblock]].state = RS_FREE; + } + ftl_map[lblock] = sb; + } + ftl_cxt.maxusn = maxusn; + return maxusn; +} + +/* phase C: place the logs, one per logical block */ +static int ftl_restore_place_logs(uint32_t maxusn) +{ + /* Static for the same reason as ftl_rs_slots[]: one struct ftl_log is + * about 2KB, too much to put on the stack. Phase C alone uses it. */ + static struct ftl_log cand; + uint32_t sb, i, lblock; + + for (i = 0; i < FTL_RS_SLOTS; i++) + ftl_rs_slots[i].lblock = 0xffff; + for (sb = 0; sb < nsuperblocks; sb++) + { + struct ftl_rs *r = &ftl_rs[sb]; + uint32_t datausn = 0; + + if (r->state != RS_LOG) + continue; + lblock = r->lblock; + if (ftl_map[lblock] == 0xffff) + { + if (ftl_log_in_place(sb)) + { + ftl_map[lblock] = sb; + r->state = RS_DATA; + continue; + } + } + else + datausn = ftl_rs[ftl_map[lblock]].usn; + if (r->usn < datausn) + { + r->state = RS_FREE; + continue; + } + for (i = 0; i < FTL_RS_SLOTS; i++) + { + struct ftl_log *s = &ftl_rs_slots[i]; + + if (s->lblock == 0xffff) + { + s->lblock = lblock; + s->sb = sb; + s->usn = r->usn; + break; + } + if (s->lblock != lblock) + continue; + if (r->usn != maxusn && s->usn != maxusn) + { + /* Neither was being written last: the newer one stays */ + if (s->usn >= r->usn) + r->state = RS_FREE; + else + { + ftl_rs[s->sb].state = RS_FREE; + s->sb = sb; + s->usn = r->usn; + } + } + else + { + /* One was being written last. It stays only if it holds + * every page the other does; otherwise it is a partial + * copy and the other still has the data. */ + const struct ftl_log *newer, *older; + uint32_t idx, loserusn; + + cand.sb = sb; + cand.usn = r->usn; + ftl_scan_log(&cand); + ftl_scan_log(s); + newer = r->usn == maxusn ? &cand : s; + older = newer == &cand ? s : &cand; + for (idx = 0; idx < sbpages; idx++) + if (older->offsets[idx] != FTL_NO_PAGE + && newer->offsets[idx] == FTL_NO_PAGE) + break; + loserusn = idx == sbpages ? older->usn : newer->usn; + if (loserusn == r->usn) + r->state = RS_FREE; + else + { + ftl_rs[s->sb].state = RS_FREE; + s->sb = sb; + s->usn = r->usn; + } + } + break; + } + if (i == FTL_RS_SLOTS) + return -31; + } + /* An eighteenth log: drop the one being written last - which may be the + * eighteenth itself - or fail */ + if (ftl_rs_slots[FTL_MAX_LOGS].lblock != 0xffff) + { + for (i = 0; i <= FTL_MAX_LOGS; i++) + if (ftl_rs_slots[i].usn == maxusn) + break; + if (i > FTL_MAX_LOGS) + { + logf("ftl: restore found more than %d logs", FTL_MAX_LOGS); + return -32; + } + ftl_rs[ftl_rs_slots[i].sb].state = RS_FREE; + if (i < FTL_MAX_LOGS) + ftl_rs_slots[i] = ftl_rs_slots[FTL_MAX_LOGS]; + ftl_rs_slots[FTL_MAX_LOGS].lblock = 0xffff; + } + return 0; +} + +/* phase D: a logical block with no data block gets the next free one. The + * blocks handed out here are the holes ftl_is_hole() reports; the caller + * erases them when the mount is writable (phase H). */ +static int ftl_restore_fill_holes(void) +{ + uint32_t lblock, f = 0; + + for (lblock = 0; lblock < usersb; lblock++) + { + if (ftl_map[lblock] != 0xffff) + continue; + while (f < nsuperblocks && ftl_rs[f].state != RS_FREE) + f++; + if (f == nsuperblocks) + return -33; + ftl_rs[f].state = RS_DATA; + ftl_rs[f].lblock = lblock; + ftl_map[lblock] = f; + ftl_hole[lblock / 8] |= 1 << (lblock % 8); + ftl_nholes++; + } + return 0; +} + +/* phase E: the free pool is everything left, after one slot per log */ +static int ftl_restore_build_pool(void) +{ + uint32_t sb, i, n; + + ftl_nlogs = 0; + for (i = 0; i < FTL_MAX_LOGS; i++) + if (ftl_rs_slots[i].lblock != 0xffff) + ftl_logs[ftl_nlogs++] = ftl_rs_slots[i]; + n = 0; + for (i = 0; i < ftl_nlogs; i++) + ftl_cxt.blockpool[i] = 0xffff; + for (sb = 0; sb < nsuperblocks && ftl_nlogs + n < FTL_POOL_SIZE; sb++) + if (ftl_rs[sb].state == RS_FREE) + ftl_cxt.blockpool[ftl_nlogs + n++] = sb; + ftl_cxt.freecount = n; + ftl_cxt.nextfreeidx = ftl_nlogs % FTL_POOL_SIZE; + if (ftl_nlogs + n != FTL_POOL_SIZE) + { + logf("ftl: restore found %u logs and %lu free blocks", ftl_nlogs, + (unsigned long)n); + return -34; + } + return 0; +} + +/* phase G: the newest erase counter, read counter and stats pages, walking + * the control blocks newest first and each one backwards */ +static void ftl_restore_side_pages(void) +{ + /* One flag per side-table slot. Static only to keep it off the mount's + * stack; nothing outside this phase reads it. */ + static uint32_t seen[2 * FTL_CXT_PAGELIST + 1]; + uint32_t i, k, blk, order[3], nec; + int type; + + memset(seen, 0, sizeof(seen)); + nec = (nsuperblocks * 2 + pagesize - 1) / pagesize; + for (k = 0; k < 3; k++) + order[k] = (ftl_ctrl_idx + 3 - k) % 3; + for (k = 0; k < 3; k++) + { + blk = ftl_ctrl[order[k]]; + for (i = sbpages; i-- > 0; ) + { + uint32_t idx, slot; + + type = ftl_read_vpage(blk, i); + idx = ftl_meta[1] & 0xffff; + if (type == SPARE_FTL_ERASECTR && idx < nec) + slot = idx; + else if (type == SPARE_FTL_READCOUNT && idx < nec) + slot = FTL_CXT_PAGELIST + idx; + else if (type == SPARE_FTL_STATS) + slot = 2 * FTL_CXT_PAGELIST; + else + continue; + if (seen[slot]) + continue; + seen[slot] = 1; + if (type == SPARE_FTL_ERASECTR) + ftl_cxt.ecpages[idx] = ftl_vpn(blk, i); + else if (type == SPARE_FTL_READCOUNT) + ftl_cxt.rcpages[idx] = ftl_vpn(blk, i); + else + { + ftl_cxt.statspage = ftl_vpn(blk, i); + ftl_cxt.statsflag = 0xffffffff; + } + } + } +} + +/* phase I: what each log holds */ +static void ftl_restore_scan_logs(void) +{ + uint32_t i; + + for (i = 0; i < ftl_nlogs; i++) + { + ftl_scan_log(&ftl_logs[i]); + ftl_logs[i].pagesused = sbpages; + ftl_logs[i].issequential = 0; + } +} + +static int ftl_restore(void) +{ + uint32_t maxusn; + int ret; + + logf("ftl: restoring from the medium"); + memset(ftl_rs, 0, sizeof(ftl_rs)); + memset(ftl_hole, 0, sizeof(ftl_hole)); + ftl_nholes = 0; + + ret = ftl_restore_ctrl_blocks(); /* phase A */ + if (ret) + return ret; + maxusn = ftl_restore_classify(); /* phase B */ + ret = ftl_restore_place_logs(maxusn); /* phase C */ + if (ret) + return ret; + ret = ftl_restore_fill_holes(); /* phase D */ + if (ret) + return ret; + ret = ftl_restore_build_pool(); /* phase E */ + if (ret) + return ret; + ftl_restore_side_pages(); /* phase G */ + ftl_restore_scan_logs(); /* phase I */ + ftl_cxt.cleanflag = 0; + return 0; +} + +/* Which superblock and page hold lpn: the newest log that has it, else the + * block map */ +static int ftl_resolve(uint32_t lpn, uint32_t *sb, uint32_t *v) +{ + uint32_t lblock = lpn / sbpages, idx = lpn % sbpages, i; + uint32_t bestusn = 0; + bool found = false; + + if (lblock >= usersb) + return -1; + *sb = ftl_map[lblock]; + *v = idx; + for (i = 0; i < ftl_nlogs; i++) + { + const struct ftl_log *log = &ftl_logs[i]; + if (log->lblock == lblock && log->offsets[idx] != FTL_NO_PAGE + && (!found || log->usn > bestusn)) + { + *sb = log->sb; + *v = log->offsets[idx]; + bestusn = log->usn; + found = true; + } + } + return 0; +} + +/* ------------------------------------------------------------------ */ +/* Write path */ +/* */ +/* A write goes to a log superblock for its logical block. Small */ +/* writes append pages to the log; a write covering a whole logical */ +/* block goes straight into a fresh superblock. A log is folded back */ +/* into the block map when it fills, when its slot is needed, and at */ +/* ftl_sync(); a commit records any log still open. */ +/* */ +/* Commits are in exactly Apple's format, so its own firmware still */ +/* mounts the result. */ +/* ------------------------------------------------------------------ */ + +/* Read one of the context's pointer lists: npages pages of type, holding + * entries 16-bit table entries between them */ +static int ftl_read_side(const uint32_t *list, uint32_t npages, uint32_t type, + uint16_t *table, uint32_t entries) +{ + uint32_t per = pagesize / 2; + uint32_t i, vpn, n; + + for (i = 0; i < npages; i++) + { + vpn = list[i]; + if (vpn / sbpages >= nsuperblocks + || ftl_read_vpage(vpn / sbpages, vpn % sbpages) != (int)type + || (ftl_meta[1] & 0xffff) != i) + return -1; + n = MIN(entries - i * per, per); + memcpy(table + i * per, ftl_buffer, n * 2); + } + return 0; +} + +/* Load the rest of what a commit has to reproduce: the erase and read + * counters and the stats page. Failure is not fatal - the FTL stays mounted + * read-only. */ +static void ftl_open_write_state(void) +{ + uint32_t vpn; + + ftl_writable = false; + ftl_nmappages = (usersb * 2 + pagesize - 1) / pagesize; + ftl_necpages = (nsuperblocks * 2 + pagesize - 1) / pagesize; + ftl_nrcpages = ftl_necpages; + ftl_nlogpages = (sbpages * FTL_MAX_LOGS * 2 + pagesize - 1) / pagesize; + + if (ftl_necpages > FTL_SIDE_PAGES || ftl_nlogpages > FTL_LOG_PAGES + || nsuperblocks > FTL_SIDE_ENTRIES) + return; + + /* Lost counter tables read as zero, as the firmware's restore leaves + * them */ + if (ftl_read_side(ftl_cxt.ecpages, ftl_necpages, SPARE_FTL_ERASECTR, + ftl_erasectr, nsuperblocks)) + { + logf("ftl: erase counters lost"); + memset(ftl_erasectr, 0, sizeof(ftl_erasectr)); + } + if (ftl_read_side(ftl_cxt.rcpages, ftl_nrcpages, SPARE_FTL_READCOUNT, + ftl_readcount, nsuperblocks)) + { + logf("ftl: read counters lost"); + memset(ftl_readcount, 0, sizeof(ftl_readcount)); + } + + /* The stats page is optional; an all-0xff one is harmless */ + memset(ftl_stats, 0xff, sizeof(ftl_stats)); + vpn = ftl_cxt.statspage; + if (ftl_cxt.statsflag == 0xffffffff && vpn / sbpages < nsuperblocks + && ftl_read_vpage(vpn / sbpages, vpn % sbpages) == SPARE_FTL_STATS) + memcpy(ftl_stats, ftl_buffer, pagesize); + + /* A spare superblock is free or a log; more than 20 of them means the + * context and the medium disagree, and we stay read-only */ + if (ftl_cxt.freecount + ftl_nlogs > FTL_POOL_SIZE) + { + logf("ftl: %lu free + %u logs > %d", (unsigned long)ftl_cxt.freecount, + ftl_nlogs, FTL_POOL_SIZE); + return; + } + ftl_writable = true; +} + +/* ---- VFL write side: replacing blocks that have gone bad ----------- */ +/* Ported from ftl-nano2g.c (ftl_vfl_store_cxt, ftl_vfl_commit_cxt, + * ftl_vfl_remap_block). The Nano 3G keeps one remap table per plane rather + * than a single table plus a bad-block bitmap, and has no bbt to maintain, + * so the table update differs; everything else is the same scheme. */ + +/* Write 8 copies of a bank's VFL context; it counts as stored if at least 5 + * read back intact, as Apple's _StoreVFLCxt requires. */ +static int ftl_vfl_store_cxt(uint32_t bank) +{ + struct ftl_vfl_cxt *cxt = &ftl_vfl_cxt[bank]; + uint32_t meta[NAND_META_WORDS]; + uint32_t i, block, first, good = 0; + + first = cxt->nextcxtpage; + cxt->updatecount--; + cxt->usn = ++ftl_vfl_usn; + cxt->nextcxtpage += 8; + ftl_vfl_calculate_checksum(cxt, &cxt->checksum1, &cxt->checksum2); + + if (cxt->activecxtblock >= 4) + return -1; + block = cxt->vflcxtblocks[cxt->activecxtblock]; + if (block >= geo->blocks || cxt->nextcxtpage > geo->pagesperblock) + return -1; + + /* Eight copies in page order, as Apple's _WriteVFLCxtPage writes them: + * pages of an MLC block must be programmed in order. The first failed + * program abandons this block. */ + meta[0] = cxt->updatecount; + meta[1] = 0xffffffff; + meta[2] = 0xffff0000 | (SPARE_VFL_CXT << 8); /* byte 8 is 0 here */ + memset(ftl_cxtpage, 0xff, pagesize); + memcpy(ftl_cxtpage, cxt, sizeof(*cxt)); + for (i = 0; i < 8; i++) + if (nand_write_page(bank, block * geo->pagesperblock + first + i, + ftl_cxtpage, meta)) + return -1; + for (i = 0; i < 8; i++) + { + if (ftl_raw_read(bank, block * geo->pagesperblock + first + i) + != SPARE_VFL_CXT + || (ftl_meta[2] & 0xff) != 0 + || ftl_meta[0] != cxt->updatecount + || memcmp(ftl_buffer, cxt, offsetof(struct ftl_vfl_cxt, + checksum1))) + continue; + good++; + } + /* Apple accepts a commit with at most three bad copies */ + return good >= 5 ? 0 : -1; +} + +/* Commit a bank's VFL context, moving to another block of the context ring + * if the current one is full or will not take it. */ +static int ftl_vfl_commit_cxt(uint32_t bank) +{ + struct ftl_vfl_cxt *cxt = &ftl_vfl_cxt[bank]; + uint32_t start = cxt->activecxtblock, i, j; + + /* The mount takes the control block list from whichever bank's context + * is newest, and this commit is about to make one of them newest, so + * every bank's copy has to agree first. Same as nano2g's + * ftl_store_ctrl_block_list(). */ + for (i = 0; i < geo->banks; i++) + memcpy(ftl_vfl_cxt[i].ftlctrlblocks, ftl_ctrl, sizeof(ftl_ctrl)); + + if ((uint32_t)cxt->nextcxtpage + 8 <= geo->pagesperblock + && !ftl_vfl_store_cxt(bank)) + return 0; + + i = start; + while (1) + { + i = (i + 1) & 3; + if (i == start) + break; + if (cxt->vflcxtblocks[i] >= geo->blocks) + continue; + for (j = 0; j < 4; j++) + if (!nand_erase_block(bank, cxt->vflcxtblocks[i])) + break; + if (j == 4) + continue; + cxt->activecxtblock = i; + cxt->nextcxtpage = 0; + if (!ftl_vfl_store_cxt(bank)) + return 0; + } + /* The RAM remap table has already changed. Continuing after a failed + * commit would use mappings that cannot survive a reboot. Stop here, + * as ftl_vfl_commit_cxt() does on the Nano 2G. */ + panicf("VFL: Failed to commit VFL CXT on bank %lu!", + (unsigned long)bank); + return -1; +} + +/* Take a physical block off a context's pending-bad list */ +static void ftl_vfl_unpend(struct ftl_vfl_cxt *cxt, uint32_t block) +{ + uint32_t i, j; + + for (i = 0; i < cxt->pendingcount && i < FTL_VFL_PENDING; ) + if (cxt->pending[i] == block) + { + for (j = i; j + 1 < cxt->pendingcount && j + 1 < FTL_VFL_PENDING; + j++) + cxt->pending[j] = cxt->pending[j + 1]; + cxt->pendingcount--; + } + else + i++; +} + +/* Replace a virtual block that has gone bad with one of the VFL's reserved + * spares, and commit so the replacement survives a remount. The unit's own + * table is used first, then any other with a free slot. */ +static int ftl_vfl_remap_block(uint32_t bank, uint32_t unit, uint32_t vblock) +{ + struct ftl_vfl_cxt *cxt = &ftl_vfl_cxt[bank]; + uint32_t block = ftl_unit_block(unit, vblock); + uint32_t n = geo->planes * geo->vflspares; + uint32_t i, k, slot = n, newblock; + + for (i = 0; i < n && slot == n; i++) + { + k = (unit * geo->vflspares + i) % n; + if (cxt->remap[k] == FTL_VFL_FREE) + slot = k; + } + if (slot == n) + { + logf("ftl: bank %lu is out of spare blocks", (unsigned long)bank); + return -1; + } + newblock = ftl_vfl_spare_block(slot); + + for (i = 0; i < 9; i++) + if (!nand_erase_block(bank, newblock)) + break; + if (i == 9) + { + cxt->remap[slot] = FTL_VFL_BAD; /* the spare itself is no good */ + cxt->badcount++; + ftl_vfl_commit_cxt(bank); + return -1; + } + /* Replaced now, so no longer pending: Apple's list holds the block as + * it was addressed, found before its spare is retired below */ + ftl_vfl_unpend(cxt, ftl_vfl_phys_block(bank, unit, vblock)); + /* A spare that was already tried for this block and failed is unusable */ + for (k = 0; k < n; k++) + if (cxt->remap[k] == block) + { + cxt->remap[k] = FTL_VFL_BAD; + cxt->badcount++; + } + cxt->remap[slot] = block; + cxt->usedcount[slot / geo->vflspares]++; + logf("ftl: remapped bank %lu block %lu to spare %lu", + (unsigned long)bank, (unsigned long)block, (unsigned long)newblock); + return ftl_vfl_commit_cxt(bank); +} + +/* Erase one virtual block, retrying and then replacing it if it will not + * take an erase. nano2g tries three times before giving up on a block. A + * block on the pending-bad list, put there by Apple's firmware after a + * failed read or write, is replaced first, as the firmware's VFL erase does. */ +static int ftl_vfl_erase(uint32_t bank, uint32_t unit, uint32_t vblock) +{ + const struct ftl_vfl_cxt *cxt = &ftl_vfl_cxt[bank]; + uint32_t block = ftl_vfl_phys_block(bank, unit, vblock); + uint32_t i; + + for (i = 0; i < cxt->pendingcount && i < FTL_VFL_PENDING; i++) + if (cxt->pending[i] == block) + { + if (ftl_vfl_remap_block(bank, unit, vblock)) + return -1; + break; + } + + for (i = 0; i < 3; i++) + if (!nand_erase_block(bank, ftl_vfl_phys_block(bank, unit, vblock))) + return 0; + logf("ftl: erase of bank %lu unit %lu vblock %lu failed, remapping", + (unsigned long)bank, (unsigned long)unit, (unsigned long)vblock); + if (ftl_vfl_remap_block(bank, unit, vblock)) + return -1; + for (i = 0; i < 3; i++) + if (!nand_erase_block(bank, ftl_vfl_phys_block(bank, unit, vblock))) + return 0; + return -1; +} + +/* Erase every physical block of a superblock and count it */ +static int ftl_erase_sb(uint32_t sb) +{ + uint32_t bank, plane; + int rc; + + if (sb >= nsuperblocks) + return -1; + for (bank = 0; bank < geo->banks; bank++) + for (plane = 0; plane < geo->planes; plane++) + { + rc = ftl_vfl_erase(bank, plane, sb); + if (rc) + { + logf("ftl: erase of sb %lu bank %lu failed (%d)", + (unsigned long)sb, (unsigned long)bank, rc); + return -1; + } + } + if (ftl_erasectr[sb] != 0xffff) + ftl_erasectr[sb]++; + return 0; +} + +/* The usn a user data page is written with, as the firmware's write path + * numbers them: one counter, advanced whenever a write starts a new + * superblock, so + * every page written until the next such start shares it */ +static uint32_t ftl_data_usn(uint32_t v) +{ + if (v == 0) + ftl_cxt.maxusn++; + return ftl_cxt.maxusn; +} + +/* ---- page runs -------------------------------------------------------- */ +/* Pages are programmed in runs, as Apple's FTL does it: the callers gather + * consecutive pages of one superblock with ftl_run_add(), and ftl_run_write() + * hands them to the controller the way the firmware's multi-page VFL write + * does, so + * that the chips program in parallel and whole rows two planes at a time. */ + +/* Queue page v of superblock sb. Pages must follow on from the ones already + * queued, in the same superblock. buf must stay put until the run is + * written. */ +static void ftl_run_add(uint32_t sb, uint32_t v, const void *buf, + uint32_t word0, uint32_t word1, uint32_t type) +{ + uint32_t *meta = ftl_run_meta[ftl_run_n]; + + if (!ftl_run_n) + { + ftl_run_sb = sb; + ftl_run_v = v; + } + meta[0] = word0; + meta[1] = word1; + meta[2] = 0xffff00ff | (type << 8); + ftl_run[ftl_run_n].buf = buf; + ftl_run[ftl_run_n].meta = meta; + ftl_run_n++; +} + +/* Program n queued pages from the first'th in one driver call. With rows, + * n is a whole number of rows; they go two-plane, in the bank-major order + * the firmware's sequential write uses, when the chip allows it and no + * block of the superblock is remapped - a spare is not the other plane's + * twin. On + * failure, *failbank and *failplanes say which blocks were being + * programmed. */ +static int ftl_run_program(uint32_t first, uint32_t n, bool rows, + uint32_t *failbank, uint32_t *failplanes) +{ + static struct nand_write w[FTL_MAX_SBPAGES]; + static uint16_t vs[FTL_MAX_SBPAGES]; + uint32_t perrow = geo->banks * geo->planes; + uint32_t i, idx, bank, plane; + bool twoplane = rows && geo->twoplane; + int rc; + + for (bank = 0; twoplane && bank < geo->banks; bank++) + for (plane = 0; plane < geo->planes; plane++) + if (ftl_vfl_phys_block(bank, plane, ftl_run_sb) + != ftl_unit_block(plane, ftl_run_sb)) + twoplane = false; + + for (i = 0; i < n; i++) + { + idx = first + i; + if (twoplane) + { + /* Entry k of a row is bank k / planes, plane k % planes */ + uint32_t k = i % perrow; + + idx = first + i - k + (k % geo->planes) * geo->banks + + k / geo->planes; + } + w[i] = ftl_run[idx]; + vs[i] = ftl_run_v + idx; + ftl_vpage_phys(ftl_run_sb, vs[i], &w[i].bank, &w[i].page); + } + + *failbank = w[0].bank; + rc = nand_write_pages(w, n, twoplane, failbank); + if (rc) + { + *failplanes = 0; + for (i = 0; i < n; i++) + if (w[i].bank == *failbank) + *failplanes |= 1 << ((vs[i] / geo->banks) % geo->planes); + } + return rc; +} + +/* Program the queued run: pages up to the next row boundary on their own, + * whole rows together while more than one row remains, and the rest on + * their own, exactly as the firmware's multi-page VFL write splits it. */ +static int ftl_run_write(bool remap_on_fail) +{ + uint32_t perrow = geo->banks * geo->planes; + uint32_t n = ftl_run_n, done = 0, len, bank = 0, planes = 0, plane; + int rc = 0; + + ftl_run_n = 0; + if (!n) + return 0; + len = MIN(perrow - ftl_run_v % perrow, n); + if (len != perrow) + { + rc = ftl_run_program(0, len, false, &bank, &planes); + done = len; + } + if (!rc && n - done > perrow) + { + len = (n - done) / perrow * perrow; + rc = ftl_run_program(done, len, true, &bank, &planes); + done += len; + } + if (!rc && n > done) + rc = ftl_run_program(done, n - done, false, &bank, &planes); + if (rc) + { + /* Only a fresh merge destination can be discarded and rebuilt. + * Live logs and control blocks must retain their earlier pages. */ + if (!remap_on_fail) + panicf("FTL: Write error: %lu %lu!", + (unsigned long)ftl_run_sb, (unsigned long)ftl_run_v); + logf("ftl: write to sb %lu bank %lu failed (%d), remapping", + (unsigned long)ftl_run_sb, (unsigned long)bank, rc); + /* The blocks will not take programs any more. Replace them from the + * VFL's spares now; whatever was already written to the superblock + * is lost, so the caller has to start it again. */ + for (plane = 0; plane < geo->planes; plane++) + if (planes & (1 << plane)) + ftl_vfl_remap_block(bank, plane, ftl_run_sb); + return -1; + } + return 0; +} + +/* Program one page on its own */ +static int ftl_write_vpage(uint32_t sb, uint32_t v, const void *buf, + uint32_t word0, uint32_t word1, uint32_t type, + bool remap_on_fail) +{ + ftl_run_add(sb, v, buf, word0, word1, type); + return ftl_run_write(remap_on_fail); +} + +/* Take a block off the free ring, least-worn first. The firmware's free-block + * take picks + * the entry with the lowest erase count and swaps it to the head of the ring + * before returning it; doing the same spreads wear over the pool instead of + * cycling through it blindly, which matters because a block reclaimed from + * cold data can join the pool thousands of cycles behind the rest. */ +static int ftl_pool_alloc(uint32_t *sb, bool worn_first) +{ + uint32_t i, idx, best = 0; + uint32_t bestec = worn_first ? 0 : 0xffffffff; + bool found = false; + uint16_t t; + + if (!ftl_cxt.freecount) + return -1; + for (i = 0; i < ftl_cxt.freecount; i++) + { + uint32_t ec; + + idx = (ftl_cxt.nextfreeidx + i) % FTL_POOL_SIZE; + if (ftl_cxt.blockpool[idx] >= nsuperblocks) + continue; + ec = ftl_erasectr[ftl_cxt.blockpool[idx]]; + if (!found || (worn_first ? ec > bestec : ec < bestec)) + { + bestec = ec; + best = idx; + found = true; + } + } + if (!found) + return -1; + + idx = ftl_cxt.nextfreeidx; + t = ftl_cxt.blockpool[idx]; + ftl_cxt.blockpool[idx] = ftl_cxt.blockpool[best]; + ftl_cxt.blockpool[best] = t; + *sb = ftl_cxt.blockpool[idx]; + + /* Pool blocks are erased when released, and the whole pool again by a + * restore, as the firmware's free-block return and its restore do */ + ftl_cxt.nextfreeidx = (ftl_cxt.nextfreeidx + 1) % FTL_POOL_SIZE; + ftl_cxt.freecount--; + return 0; +} + +static int ftl_pool_release(uint32_t sb) +{ + if (ftl_cxt.freecount >= FTL_POOL_SIZE) + { + logf("ftl: releasing sb %lu with a full pool", (unsigned long)sb); + return -1; + } + if (ftl_erase_sb(sb)) + return -1; + ftl_cxt.blockpool[(ftl_cxt.nextfreeidx + ftl_cxt.freecount) + % FTL_POOL_SIZE] = sb; + ftl_cxt.freecount++; + return 0; +} + +static struct ftl_log *ftl_get_log_entry(uint32_t lblock); + +/* The current contents of lpn, for copying into a merged block. A sector + * that was never written reads as 0xff. */ +static int ftl_read_current(uint32_t lpn, uint8_t *dst) +{ + uint32_t sb, v, bank, page, meta[NAND_META_WORDS]; + int rc; + + if (ftl_resolve(lpn, &sb, &v)) + return -1; + if (ftl_nholes && sb == ftl_map[lpn / sbpages] + && ftl_is_hole(lpn / sbpages)) + { + memset(dst, 0xff, pagesize); + return 0; + } + ftl_vpage_phys(sb, v, &bank, &page); + rc = nand_read_page(bank, page, dst, meta); + if (rc < 0 || rc == NAND_ECC_FAILED) + return -1; + if (meta_type(meta) == SPARE_ERASED) + { + memset(dst, 0xff, pagesize); + return 0; + } + if (!meta_is_data(meta) || meta[0] != lpn) + { + logf("ftl: merge read lpn %lx from sb %lu v %lu: type %lx lpn %lx", + (unsigned long)lpn, (unsigned long)sb, (unsigned long)v, + (unsigned long)meta_type(meta), (unsigned long)meta[0]); + return -1; + } + return 0; +} + +/* Read one prepared part of a run. Apple retries a failed multi-page read + * page by page; keep the BootROM-derived single-page path for that retry. */ +static int ftl_read_prepared(uint32_t first, uint32_t n, + const uint32_t *lpns) +{ + uint32_t i; + int failed = nand_read_pages(ftl_read_run + first, n) != 0; + + for (i = first; i < first + n; i++) + { + struct nand_read *r = &ftl_read_run[i]; + uint32_t *meta = ftl_read_meta[i]; + + if (failed || r->ecc == NAND_ECC_FAILED) + { + r->ecc = nand_read_page(r->bank, r->page, r->buf, meta); + if (r->ecc < 0 || r->ecc == NAND_ECC_FAILED) + return -1; + } + /* Corrected data is safe to return: the controller has repaired it. + * TODO: Implement Apple's refresh list so persistently corrected + * blocks are proactively rewritten. This is media maintenance, not + * a loss of ECC protection compared with the earlier read path. */ + if (meta_type(meta) == SPARE_ERASED) + memset(r->buf, 0xff, pagesize); + else if (!meta_is_data(meta) || meta[0] != lpns[i]) + { + logf("ftl: lpn %lx: spare says %lx type %lx", + (unsigned long)lpns[i], (unsigned long)meta[0], + (unsigned long)meta_type(meta)); + return -1; + } + } + return 0; +} + +/* Read current logical pages into consecutive buffers. With a log the + * firmware uses one scattered VFL request. Without one, its multi-page VFL + * read splits at a four-bank row: an unaligned head, whole rows only while + * more than one row remains, then the final row/tail. */ +static int ftl_read_pages(uint32_t lpn, uint32_t n, uint8_t *dst) +{ + static uint32_t lpns[FTL_MAX_SBPAGES]; + uint32_t i, nr = 0, sb, v, bank, page, head, middle; + uint32_t lblock = lpn / sbpages; + bool scattered = ftl_get_log_entry(lblock) != NULL; + + if (!n || n > FTL_MAX_SBPAGES || lblock != (lpn + n - 1) / sbpages) + return -1; + for (i = 0; i < n; i++) + { + if (ftl_resolve(lpn + i, &sb, &v)) + return -1; + if (ftl_nholes && sb == ftl_map[lblock] && ftl_is_hole(lblock)) + { + memset(dst + i * pagesize, 0xff, pagesize); + continue; + } + ftl_vpage_phys(sb, v, &bank, &page); + ftl_read_run[nr].bank = bank; + ftl_read_run[nr].page = page; + ftl_read_run[nr].buf = dst + i * pagesize; + ftl_read_run[nr].meta = ftl_read_meta[nr]; + lpns[nr++] = lpn + i; + } + if (!nr) + return 0; + if (scattered) + return ftl_read_prepared(0, nr, lpns); + + v = lpn % sbpages; + head = MIN(nr, geo->banks - v % geo->banks); + if (head == geo->banks) + head = 0; + if (head && ftl_read_prepared(0, head, lpns)) + return -1; + i = head; + middle = nr - i > geo->banks + ? ((nr - i - 1) / geo->banks) * geo->banks : 0; + if (middle && ftl_read_prepared(i, middle, lpns)) + return -1; + i += middle; + return i == nr ? 0 : ftl_read_prepared(i, nr - i, lpns); +} + +/* Drop the log blocks of a logical block, returning them to the pool */ +static int ftl_release_logs(uint32_t lblock) +{ + unsigned int i = 0; + int ret = 0; + + while (i < ftl_nlogs) + { + if (ftl_logs[i].lblock != lblock) + { + i++; + continue; + } + if (ftl_pool_release(ftl_logs[i].sb)) + ret = -1; + ftl_logs[i] = ftl_logs[--ftl_nlogs]; + } + return ret; +} + +/* Does src() have new contents for every page of this logical block? Then + * the block can be written outright and no log is needed - the one case + * where there is no write amplification at all. */ +static bool ftl_block_complete(uint32_t lblock, ftl_write_src src, void *ctx) +{ + uint32_t v; + + for (v = 0; v < sbpages; v++) + if (!src(lblock * sbpages + v, ctx)) + return false; + return true; +} + +/* Write every page of a logical block into sb, taking new contents from + * src() where it has them and the block's current contents where it does + * not. New contents for the whole block go in one run, as the firmware's write + * path writes + * them; a copy goes an eighth of the block at a time, read and then + * written, as the firmware's simple merge does. */ +static int ftl_fill_block(uint32_t lblock, uint32_t sb, ftl_write_src src, + void *ctx) +{ + bool whole = src && ftl_block_complete(lblock, src, ctx); + uint32_t v, lpn, n = 0; + + for (v = 0; v < sbpages; v++) + { + const uint8_t *new_data; + + lpn = lblock * sbpages + v; + new_data = src ? src(lpn, ctx) : NULL; + if (!new_data) + { + new_data = ftl_copybuf + n * pagesize; + if (!src && n == 0 + && ftl_read_pages(lpn, MIN(sbpages - v, sbpages / 8), + ftl_copybuf)) + { + ftl_run_n = 0; + return -1; + } + if (src && ftl_read_current(lpn, ftl_copybuf + n * pagesize)) + { + ftl_run_n = 0; + return -1; + } + } + ftl_run_add(sb, v, new_data, lpn, ftl_data_usn(v), + v == sbpages - 1 ? SPARE_DATA_LAST : SPARE_DATA); + if (!whole && ++n == sbpages / 8) + { + if (ftl_run_write(true)) + return -1; + n = 0; + } + } + return ftl_run_write(true); +} + +static int ftl_merge(uint32_t lblock, ftl_write_src src, void *ctx, + bool worn_first) +{ + uint32_t sb, old; + int attempt; + + if (lblock >= usersb) + return -1; + old = ftl_map[lblock]; + + /* Up to four attempts, each in a different superblock. A block that + * refused the write has been remapped by ftl_run_write() by the time + * we get here, so the next attempt gets fresh silicon. + * ftl_commit_scattered() in ftl-nano2g.c retries the same way. */ + for (attempt = 0; attempt < 4; attempt++) + { + if (ftl_pool_alloc(&sb, worn_first)) + { + logf("ftl: free pool empty"); + return -1; + } + if (!ftl_fill_block(lblock, sb, src, ctx)) + break; + logf("ftl: lblock %lu did not take, trying another superblock", + (unsigned long)lblock); + if (ftl_pool_release(sb)) + return -1; + } + if (attempt == 4) + { + logf("ftl: lblock %lu failed in four superblocks", + (unsigned long)lblock); + return -1; + } + + /* Only now is the new block complete enough to point at */ + ftl_map[lblock] = sb; + if (ftl_release_logs(lblock) || ftl_pool_release(old)) + return -1; + return 0; +} + +/* Does src() have anything for this logical block? */ +static bool ftl_block_dirty(uint32_t lblock, ftl_write_src src, void *ctx) +{ + uint32_t v; + + for (v = 0; v < sbpages; v++) + if (src(lblock * sbpages + v, ctx)) + return true; + return false; +} + +/* ---- log blocks --------------------------------------------------- */ +/* Ported from ftl-nano2g.c, which implements the same Whimory scheme. Small + * writes append pages to a log superblock instead of rewriting the whole + * logical block; the log is folded back only when it fills, when its slot is + * needed, or at a commit. */ + +static struct ftl_log *ftl_get_log_entry(uint32_t lblock) +{ + unsigned int i; + + for (i = 0; i < ftl_nlogs; i++) + if (ftl_logs[i].lblock == lblock) + return &ftl_logs[i]; + return NULL; +} + +/* Forget a log entry without touching its superblock */ +static void ftl_drop_log(struct ftl_log *log) +{ + *log = ftl_logs[--ftl_nlogs]; +} + +/* Fold a log into a freshly allocated data block, whatever is in it. Reading + * goes through ftl_resolve(), so the log's pages win where it has them. */ +static int ftl_commit_scattered(struct ftl_log *log) +{ + /* ftl_merge() releases every log of this logical block for us */ + return ftl_merge(log->lblock, NULL, NULL, false); +} + +/* A log written strictly in order is already a valid data block apart from + * the pages nobody has written yet: fill those in from the block it + * replaces and adopt it, saving a whole block copy and one erase. */ +static int ftl_commit_sequential(struct ftl_log *log) +{ + uint32_t lblock = log->lblock, sb = log->sb, old, v, n, i; + + if (!log->issequential || log->pagescurrent != log->pagesused) + return ftl_commit_scattered(log); + for (v = log->pagesused; v < sbpages; v++) + if (log->offsets[v] != FTL_NO_PAGE) + return ftl_commit_scattered(log); + + /* An eighth of the block at a time, read and then written, as + * the firmware's copy merge does */ + for (v = log->pagesused; v < sbpages; v += n) + { + n = MIN(sbpages - v, sbpages / 8); + if (ftl_read_pages(lblock * sbpages + v, n, ftl_copybuf)) + return ftl_commit_scattered(log); + for (i = 0; i < n; i++) + { + const uint8_t *buf = ftl_copybuf + i * pagesize; + + ftl_run_add(sb, v + i, buf, lblock * sbpages + v + i, + ftl_data_usn(v + i), + v + i == sbpages - 1 ? SPARE_DATA_LAST : SPARE_DATA); + } + if (ftl_run_write(false)) + return ftl_commit_scattered(log); + for (i = 0; i < n; i++) + log->offsets[v + i] = v + i; + log->pagesused += n; + log->pagescurrent += n; + } + + old = ftl_map[lblock]; + ftl_map[lblock] = sb; + ftl_drop_log(log); + return ftl_pool_release(old); +} + +/* Copy the live pages of a log, in index order, into a fresh superblock and + * release the old one, as the firmware's move merge and + * ftl_compact_scattered() in ftl-nano2g.c do. A log that has filled up + * mostly with superseded copies of + * a few sectors then costs only its live pages, not a whole block. If power + * is cut part way, the partial copy holds fewer pages than the log it was + * copying, and the mount keeps the log. */ +static int ftl_compact_scattered(struct ftl_log *log) +{ + static uint16_t offsets[FTL_MAX_SBPAGES]; + static uint32_t lpns[FTL_COPY_PAGES]; + uint32_t sb, idx, v = 0, n = 0, lpn, bank, page, i; + + if (!log->pagescurrent) + { + sb = log->sb; + ftl_drop_log(log); + return ftl_pool_release(sb); + } + if (ftl_pool_alloc(&sb, false)) + return -1; + for (idx = 0; idx < sbpages; idx++) + { + offsets[idx] = FTL_NO_PAGE; + if (log->offsets[idx] != FTL_NO_PAGE) + { + lpn = log->lblock * sbpages + idx; + ftl_vpage_phys(log->sb, log->offsets[idx], &bank, &page); + ftl_read_run[n].bank = bank; + ftl_read_run[n].page = page; + ftl_read_run[n].buf = ftl_copybuf + n * pagesize; + ftl_read_run[n].meta = ftl_read_meta[n]; + lpns[n] = lpn; + offsets[idx] = v + n; + n++; + } + /* Up to an eighth of a block of live pages at a time, as + * the firmware's move merge gathers them */ + if (n == sbpages / 8 || (n && idx == sbpages - 1)) + { + if (ftl_read_prepared(0, n, lpns)) + break; + for (i = 0; i < n; i++) + { + const uint8_t *buf = ftl_copybuf + i * pagesize; + ftl_run_add(sb, v, buf, lpns[i], ftl_data_usn(v), + SPARE_DATA); + v++; + } + if (ftl_run_write(true)) + break; + n = 0; + } + } + if (idx < sbpages) + { + /* Fold it instead; the log itself is untouched */ + if (ftl_pool_release(sb)) + return -1; + return ftl_commit_scattered(log); + } + lpn = log->sb; /* the old superblock, to release */ + memcpy(log->offsets, offsets, sizeof(log->offsets)); + log->sb = sb; + log->pagesused = v; + log->pagescurrent = v; + log->issequential = 1; + for (idx = 0; idx < v; idx++) + if (log->offsets[idx] != idx) + log->issequential = 0; + return ftl_pool_release(lpn); +} + +/* Free a log slot, as the firmware's merge and ftl_remove_scattered_block() in + * ftl-nano2g.c do. With no entry given, fold the oldest - the one whose + * pages have been sitting unmerged longest. A given log that is at most + * half live is compacted rather than folded. */ +static int ftl_remove_scattered_block(struct ftl_log *log) +{ + unsigned int i; + + ftl_cxt.swapcounter++; + if (!log) + { + uint32_t age = 0xffffffff; + + for (i = 0; i < ftl_nlogs; i++) + if (ftl_logs[i].usn <= age) + { + age = ftl_logs[i].usn; + log = &ftl_logs[i]; + } + if (!log) + return -1; + } + else if (log->pagescurrent <= sbpages / 2) + return ftl_compact_scattered(log); + if (log->issequential) + return ftl_commit_sequential(log); + return ftl_commit_scattered(log); +} + +/* A log for this logical block, making room if need be. */ +static struct ftl_log *ftl_allocate_log_entry(uint32_t lblock) +{ + struct ftl_log *log = ftl_get_log_entry(lblock); + uint32_t sb, i; + + if (log) + goto used; + if (ftl_nlogs == FTL_MAX_LOGS && ftl_remove_scattered_block(NULL)) + return NULL; + /* Keep enough of the pool back for a fold to be possible: Apple's + * the firmware's log preparation merges a victim when only three free + * blocks remain */ + if (ftl_cxt.freecount <= 3 && ftl_remove_scattered_block(NULL)) + return NULL; + if (ftl_nlogs == FTL_MAX_LOGS || ftl_pool_alloc(&sb, false)) + return NULL; + + log = &ftl_logs[ftl_nlogs++]; + log->sb = sb; + log->lblock = lblock; + log->pagesused = 0; + log->pagescurrent = 0; + log->issequential = 1; + memset(log->offsets, 0xff, sizeof(log->offsets)); +used: + /* Its recency, for choosing a victim, as the firmware's log preparation + * keeps it */ + log->usn = ftl_cxt.maxusn - 1; + if (!log->usn) + for (i = 0; i < ftl_nlogs; i++) + ftl_logs[i].usn = 0; + return log; +} + +/* Append one page to a log, as ftl_write() in ftl-nano2g.c does. The page + * is only queued: the firmware's write path hands everything it appends to a + * log to the VFL + * in one run, and ftl_run_write() must be called before anything else + * touches the flash. */ +static void ftl_log_append(struct ftl_log *log, uint32_t idx, + const void *data) +{ + uint32_t v = log->pagesused; + uint32_t lpn = log->lblock * sbpages + idx; + uint32_t type = SPARE_DATA; + + if (v == sbpages - 1 && log->issequential) + type = SPARE_DATA_LAST; + ftl_run_add(log->sb, v, data, lpn, ftl_data_usn(v), type); + log->pagesused++; + if (log->offsets[idx] == FTL_NO_PAGE) + log->pagescurrent++; + log->offsets[idx] = v; + if (log->pagesused != log->pagescurrent || log->offsets[idx] != idx) + log->issequential = 0; +} + +/* Static wear levelling. Allocation alone only spreads wear over blocks that + * are already being written; a block holding data nobody rewrites keeps its + * low erase count for ever while the working set grinds down. When the free + * pool has worn well ahead of the least-worn mapped block, move that block's + * data into the *most* worn free one, so the cold block joins the pool. + * One relocation per call; callers ration calls with swapcounter. */ +#define FTL_WEAR_SPREAD 64 + +static int ftl_wear_level(void) +{ + uint32_t i, sb, coldlb = usersb, coldec = 0xffff, poolmax = 0; + + if (!ftl_cxt.freecount) + return 0; + for (i = 0; i < ftl_cxt.freecount; i++) + { + sb = ftl_cxt.blockpool[(ftl_cxt.nextfreeidx + i) % FTL_POOL_SIZE]; + if (sb < nsuperblocks && ftl_erasectr[sb] > poolmax) + poolmax = ftl_erasectr[sb]; + } + for (i = 0; i < usersb; i++) + if (ftl_map[i] < nsuperblocks && ftl_erasectr[ftl_map[i]] < coldec) + { + coldec = ftl_erasectr[ftl_map[i]]; + coldlb = i; + } + if (coldlb == usersb || poolmax <= coldec + FTL_WEAR_SPREAD) + return 0; + + logf("ftl: wear levelling lblock %lu (ec %lu) vs pool max %lu", + (unsigned long)coldlb, (unsigned long)coldec, + (unsigned long)poolmax); + return ftl_merge(coldlb, NULL, NULL, true); +} + +/* Reserve n consecutive pages in a control block. A full block is erased and + * reused in place, exactly as the firmware does - no pool block and + * no VFL commit is needed for this. */ +static int ftl_ctrl_reserve(uint32_t n, uint32_t *sb, uint32_t *v) +{ + if (n > sbpages) + return -1; + if (ftl_ctrl_v + n > sbpages) + { + uint32_t next = (ftl_ctrl_idx + 1) % 3; + + if (ftl_ctrl[next] >= nsuperblocks || ftl_erase_sb(ftl_ctrl[next])) + return -1; + ftl_ctrl_idx = next; + ftl_ctrl_v = 0; + } + *sb = ftl_ctrl[ftl_ctrl_idx]; + *v = ftl_ctrl_v; + ftl_ctrl_v += n; + return 0; +} + +/* Mark the context dirty before the first write after a commit, as + * the firmware's mark-context-invalid step does. A mount, ours or the + * firmware's, then finds a 0x4f page + * after the context and runs the restore, instead of trusting a context that + * predates the writes. */ +static int ftl_mark_dirty(void) +{ + uint32_t sb, v; + + if (!ftl_cxt.cleanflag) + return 0; /* already marked */ + if (ftl_ctrl_reserve(1, &sb, &v)) + return -1; + memset(ftl_buffer, 0xff, pagesize); + if (ftl_write_vpage(sb, v, ftl_buffer, ftl_cxt.usn - 1, 0xffffffff, + SPARE_FTL_MOUNT, false)) + return -1; + ftl_cxt.cleanflag = 0; + return 0; +} + +/* Write the 25-page commit: erase counters, read counters, map, log tables, + * stats, then the context, all carrying the same usn, in Apple's order, as + * the firmware's context store does. The open logs are recorded in the + * context's log slots + * and the offset tables, so a commit need not fold them. */ +static int ftl_commit(void) +{ + uint32_t per = pagesize / 2; + uint32_t npages, usn, sb, v0, i; + uint32_t ecv, rcv, mapv, logv, statsv, cxtv; + + npages = FTL_COMMIT_PAGES(ftl_necpages, ftl_nrcpages, ftl_nmappages, + ftl_nlogpages); + if (npages > sbpages || npages * pagesize > sizeof(ftl_copybuf)) + return -1; + + if (ftl_ctrl_reserve(npages, &sb, &v0)) + return -1; + usn = ftl_cxt.usn - npages; + + ecv = v0; + rcv = ecv + ftl_necpages; + mapv = rcv + ftl_nrcpages; + logv = mapv + ftl_nmappages; + statsv = logv + ftl_nlogpages; + cxtv = statsv + 1; + + /* The whole commit goes to the flash as one run, as the firmware's context + * store writes + * it; the table pages are staged in ftl_copybuf until then */ + for (i = 0; i < ftl_necpages; i++) + { + uint8_t *buf = ftl_copybuf + (ecv - v0 + i) * pagesize; + + memset(buf, 0xff, pagesize); + memcpy(buf, ftl_erasectr + i * per, + MIN(nsuperblocks - i * per, per) * 2); + ftl_run_add(sb, ecv + i, buf, usn, 0xffff0000 | i, + SPARE_FTL_ERASECTR); + ftl_cxt.ecpages[i] = ftl_vpn(sb, ecv + i); + } + for (i = 0; i < ftl_nrcpages; i++) + { + uint8_t *buf = ftl_copybuf + (rcv - v0 + i) * pagesize; + + memset(buf, 0xff, pagesize); + memcpy(buf, ftl_readcount + i * per, + MIN(nsuperblocks - i * per, per) * 2); + ftl_run_add(sb, rcv + i, buf, usn, 0xffff0000 | i, + SPARE_FTL_READCOUNT); + ftl_cxt.rcpages[i] = ftl_vpn(sb, rcv + i); + } + for (i = 0; i < ftl_nmappages; i++) + { + uint8_t *buf = ftl_copybuf + (mapv - v0 + i) * pagesize; + + memset(buf, 0xff, pagesize); + memcpy(buf, ftl_map + i * per, MIN(usersb - i * per, per) * 2); + ftl_run_add(sb, mapv + i, buf, usn, 0xffff0000 | i, SPARE_FTL_MAP); + ftl_cxt.mappages[i] = ftl_vpn(sb, mapv + i); + } + /* One offset table per log slot, in slot order */ + memset(ftl_logtable, 0xff, sizeof(ftl_logtable)); + for (i = 0; i < ftl_nlogs; i++) + memcpy(ftl_logtable + i * sbpages, ftl_logs[i].offsets, sbpages * 2); + for (i = 0; i < ftl_nlogpages; i++) + { + ftl_run_add(sb, logv + i, ftl_logtable + i * per, usn, 0xffff0000 | i, + SPARE_FTL_LOG); + ftl_cxt.logpages[i] = ftl_vpn(sb, logv + i); + } + ftl_run_add(sb, statsv, ftl_stats, usn, 0xffff0000, SPARE_FTL_STATS); + ftl_cxt.statspage = ftl_vpn(sb, statsv); + ftl_cxt.statsflag = 0xffffffff; + ftl_cxt.cleanflag = 1; /* everything is in the map again */ + + /* The context itself */ + ftl_cxt.usn = usn; + for (i = 0; i < 3; i++) + ftl_cxt.ctrlblocks[i] = ftl_ctrl[i]; + ftl_cxt.ctrlpage = ftl_vpn(sb, cxtv); + for (i = 0; i < FTL_CXT_LOGS; i++) + { + struct ftl_cxt_log *c = &ftl_cxt.logs[i]; + + if (i < ftl_nlogs && i < FTL_MAX_LOGS) + { + c->usn = ftl_logs[i].usn; + c->sb = ftl_logs[i].sb; + c->lblock = ftl_logs[i].lblock; + c->pagesused = ftl_logs[i].pagesused; + c->pagescurrent = ftl_logs[i].pagescurrent; + c->issequential = ftl_logs[i].issequential; + } + else + { + c->sb = 0xffff; + c->lblock = 0xffff; + } + } + memset(ftl_cxtpage, 0xff, pagesize); + memcpy(ftl_cxtpage, &ftl_cxt, sizeof(ftl_cxt)); + ftl_run_add(sb, cxtv, ftl_cxtpage, usn, 0xffffffff, SPARE_FTL_CXT); + if (ftl_run_write(false)) + return -1; + + ftl_cxt.usn = usn; + return 0; +} + +/* src for a plain contiguous write of sectors. On pages larger than a + * sector, a page the write covers only part of comes from part, holding + * the page's current contents with the new sectors laid over them. */ +struct ftl_range +{ + uint32_t first; /* sectors */ + uint32_t count; + const uint8_t *buf; + uint32_t partlpn; /* the page in part, or 0xffffffff */ + const uint8_t *part; +}; + +static const void *ftl_range_src(uint32_t lpn, void *ctx) +{ + const struct ftl_range *r = ctx; + uint64_t sector = (uint64_t)lpn * secperpage; + + if (lpn == r->partlpn) + return r->part; + if (sector < r->first || sector - r->first >= r->count) + return NULL; + return r->buf + (size_t)(sector - r->first) * NAND_PAGE_SIZE; +} + +/* The single failure path of the write side. Every failure below leaves the + * medium itself consistent - the committed map still points at whatever was + * there - but our pool or log accounting may no longer match it, so the + * mount drops to read-only and the caller is told which step failed. + * Returns the code, so a failure site is one statement. */ +static int ftl_write_fail(int code) +{ + ftl_writable = false; + return code; +} + +/* One logical block of ftl_write_merge(): mark the context dirty, then + * either rewrite the block whole or append the changed pages to a log. */ +static int ftl_write_block(uint32_t lblock, ftl_write_src src, void *ctx) +{ + struct ftl_log *log; + uint32_t idx; + int ret = 0; + + /* Anything we leave in a log block is invisible to the committed map, + * so the context has to say so before a page is written. Once per block + * rather than once per call: the commit below leaves the context clean + * again, and the blocks after it need marking too. */ + if (ftl_mark_dirty()) + return ftl_write_fail(-5); + + /* A whole block at once needs no log and costs no amplification */ + if (ftl_block_complete(lblock, src, ctx)) + { + if (ftl_merge(lblock, src, ctx, false)) + return ftl_write_fail(-3); + return 0; + } + + log = ftl_allocate_log_entry(lblock); + if (!log) + return ftl_write_fail(-6); + for (idx = 0; idx < sbpages && !ret; idx++) + { + const void *data = src(lblock * sbpages + idx, ctx); + + if (!data) + continue; + if (log->pagesused == sbpages) + { + /* Full: fold it away and start another. These two cannot + * return straight away - the run queued above still has to be + * flushed below before we leave. */ + ftl_run_write(false); + if (ftl_remove_scattered_block(log)) + { + ret = ftl_write_fail(-7); + break; + } + log = ftl_allocate_log_entry(lblock); + if (!log) + { + ret = ftl_write_fail(-6); + break; + } + } + ftl_log_append(log, idx, data); + } + /* A failed program panics: the log's earlier pages cannot be + * rebuilt, as for a single page in ftl-nano2g.c */ + ftl_run_write(false); + if (ret) + return ret; + if (log->pagesused == sbpages && ftl_remove_scattered_block(log)) + return ftl_write_fail(-7); + return 0; +} + +/* Write the pages src() has new contents for, in every logical block + * between first and last. Blocks it has nothing for are left alone. */ +static int ftl_write_merge(uint32_t first, uint32_t last, ftl_write_src src, + void *ctx) +{ + uint32_t lblock; + int ret = 0; + + if (!ftl_mounted || !ftl_writable || !src) + return -1; + if (last < first || last >= usersb * sbpages) + return -2; + + mutex_lock(&ftl_mtx); + for (lblock = first / sbpages; lblock <= last / sbpages; lblock++) + { + if (!ftl_block_dirty(lblock, src, ctx)) + continue; + ret = ftl_write_block(lblock, src, ctx); + if (ret) + break; + } + /* Static wear levelling on the budget the firmware's write path and + * ftl-nano2g.c's ftl_write() use: every merge adds one, and at 300 one + * cold block is moved. There is no commit here - like Apple's firmware, + * ftl_sync() alone commits, and an unclean mount recovers the rest. */ + if (!ret && ftl_cxt.swapcounter >= 300) + { + ftl_cxt.swapcounter -= 20; + if (ftl_wear_level()) + ret = ftl_write_fail(-4); + } + mutex_unlock(&ftl_mtx); + return ret; +} + +/* Write sectors that all lie in the page lpn and do not fill it: the rest + * of the page is read back and written with them */ +static int ftl_write_part(uint32_t lpn, uint32_t sector, uint32_t count, + const uint8_t *buf) +{ + struct ftl_range r = { sector, count, buf, lpn, ftl_sectorpage }; + int rc; + + mutex_lock(&ftl_mtx); + rc = ftl_read_pages(lpn, 1, ftl_sectorpage); + mutex_unlock(&ftl_mtx); + if (rc) + return -3; + memcpy(ftl_sectorpage + (sector - lpn * secperpage) * NAND_PAGE_SIZE, + buf, count * NAND_PAGE_SIZE); + return ftl_write_merge(lpn, lpn, ftl_range_src, &r); +} + +int ftl_write(uint32_t sector, uint32_t count, const void *buffer) +{ + struct ftl_range r = { sector, count, buffer, 0xffffffff, NULL }; + const uint8_t *buf = buffer; + uint32_t n, first, last; + int rc; + + if (!count) + return 0; + if (sector + count > ftl_num_sectors() || sector + count < sector) + return -2; + + /* A page only partly covered at either end, then the whole pages */ + if (sector % secperpage) + { + n = MIN(count, secperpage - sector % secperpage); + rc = ftl_write_part(sector / secperpage, sector, n, buf); + if (rc) + return rc; + sector += n; + count -= n; + buf += n * NAND_PAGE_SIZE; + } + if (count % secperpage) + { + n = count % secperpage; + rc = ftl_write_part((sector + count - n) / secperpage, + sector + count - n, n, + buf + (count - n) * NAND_PAGE_SIZE); + if (rc) + return rc; + count -= n; + } + if (!count) + return 0; + first = sector / secperpage; + last = (sector + count) / secperpage - 1; + r.first = sector; + r.count = count; + r.buf = buf; + return ftl_write_merge(first, last, ftl_range_src, &r); +} + +/* The commit sequence, with ftl_mtx already held. */ +static int ftl_sync_locked(void) +{ + int ret = 0; + + /* Fold every log block away - the ones we filled this session and any + * left by whoever wrote the NAND last - so the commit can say there are + * none and the free pool is whole again, as the firmware's full flush and + * ftl-nano2g.c's ftl_sync() do. A log that was written in order is + * adopted outright rather than copied. */ + if (ftl_cxt.swapcounter >= 20) + { + ftl_cxt.swapcounter -= 20; + if (ftl_wear_level()) + { + ftl_writable = false; + return -6; + } + } + while (ftl_nlogs) + { + ftl_cxt.swapcounter++; + if (ftl_commit_sequential(&ftl_logs[0])) + { + ftl_writable = false; + return -2; + } + } + if (ftl_commit()) + { + ftl_writable = false; + ret = -4; + } + return ret; +} + +int ftl_sync(void) +{ + int ret; + + if (!ftl_mounted) + return -1; + /* Nothing written since the last commit: nothing to do. Shutdown calls + * this every time, and ftl-nano2g.c returns early the same way. A + * read-only mount cannot have written anything either, as with the + * Nano 2G's FTL_READONLY. */ + if (ftl_cxt.cleanflag || ftl_readonly) + return 0; + if (!ftl_writable) + return -1; + mutex_lock(&ftl_mtx); + ret = ftl_sync_locked(); + mutex_unlock(&ftl_mtx); + return ret; +} + +int ftl_read(uint32_t sector, uint32_t count, void *buffer) +{ + uint8_t *buf = buffer; + uint32_t n, lpn, off; + int ret = 0; + + if (!ftl_mounted) + return -1; + if (!count) + return 0; + if (sector + count > ftl_num_sectors() || sector + count < sector) + return -2; + + mutex_lock(&ftl_mtx); + while (count) + { + lpn = sector / secperpage; + off = sector % secperpage; + if (off || count < secperpage) + { + /* Part of a page, through a page-sized buffer */ + n = MIN(count, secperpage - off); + if (ftl_read_pages(lpn, 1, ftl_sectorpage)) + { + ret = -3; + break; + } + memcpy(buf, ftl_sectorpage + off * NAND_PAGE_SIZE, + n * NAND_PAGE_SIZE); + } + else + { + n = MIN(count / secperpage, sbpages - lpn % sbpages); + if (ftl_read_pages(lpn, n, buf)) + { + ret = -3; + break; + } + n *= secperpage; + } + sector += n; + count -= n; + buf += n * NAND_PAGE_SIZE; + } + mutex_unlock(&ftl_mtx); + return ret; +} + +bool ftl_readonly_mount(void) +{ + /* Anything but a mounted, writable FTL takes no writes: a predicate + * that guards them answers the safe way when nothing is mounted. */ + return !ftl_mounted || !ftl_writable; +} + +uint32_t ftl_num_sectors(void) +{ + return ftl_mounted ? usersb * sbpages * secperpage : 0; +} + +/* The geometry the mount works in, from the chip table row */ +static int ftl_setup_geometry(void) +{ + geo = nand_get_geometry(); + if (!geo || geo->banks == 0 || geo->banks > FTL_MAX_BANKS) + return -1; + if (geo->layout < NAND_LAYOUT_SINGLE || geo->layout > NAND_LAYOUT_SPLIT13 + || geo->planes < 1 || geo->planes > 4 + || geo->pagesize < NAND_PAGE_SIZE + || geo->pagesize > NAND_MAX_PAGE_SIZE + || geo->pagesize % NAND_PAGE_SIZE) + return -4; + pagesize = geo->pagesize; + secperpage = pagesize / NAND_PAGE_SIZE; + /* Apple's VFL reserves each unit's top vflspares virtual blocks; below + * them are the user blocks, 3 FTL control blocks and a pool of 20 */ + nsuperblocks = geo->blocks / geo->planes - geo->vflspares; + sbpages = geo->pagesperblock * geo->planes * geo->banks; + usersb = geo->userblocks / geo->planes; + if (nsuperblocks > FTL_MAX_SB || usersb > FTL_MAX_USERSB + || usersb > nsuperblocks || sbpages > FTL_MAX_SBPAGES + || sbpages * pagesize > FTL_MAX_SBPAGES * NAND_PAGE_SIZE + || geo->planes * geo->vflspares > 820) + return -2; + return 0; +} + +int ftl_init(void) +{ + uint32_t bank; + bool restored; + int rc; + + ftl_mounted = false; + mutex_init(&ftl_mtx); + ftl_nholes = 0; + + rc = ftl_setup_geometry(); + if (rc) + return rc; + + for (bank = 0; bank < geo->banks; bank++) + { + if (ftl_vfl_open_bank(bank)) + { + logf("ftl: no VFL context on bank %lu", (unsigned long)bank); + return -3; + } + if (ftl_vfl_check_cxt(&ftl_vfl_cxt[bank])) + { + logf("ftl: VFL context on bank %lu does not fit the chip", + (unsigned long)bank); + return -5; + } + } + + for (bank = 0; bank < geo->banks; bank++) + if (ftl_vfl_cxt[bank].usn > ftl_vfl_usn) + ftl_vfl_usn = ftl_vfl_cxt[bank].usn; + + /* Apple's FTL_Open: the context if it describes the medium, else + * the restore */ + rc = ftl_load_cxt(); + if (rc < 0) + { + logf("ftl: no FTL context (%d)", rc); + return -10 + rc; + } + restored = !rc; + rc = restored ? ftl_restore() : ftl_load_clean(); + if (rc) + { + logf("ftl: %s failed (%d)", restored ? "restore" : "load", rc); + return -20 + rc; + } + ftl_open_write_state(); +#ifdef BOOTLOADER + /* Bootloaders don't need write access, as on the Nano 2G */ + ftl_writable = false; +#endif + /* A chip whose row has not been proven on hardware is only read */ + if (!geo->validated) + ftl_writable = false; + ftl_readonly = !ftl_writable; + + /* The rest of the restore, which writes: erase the blocks given to + * holes (phase D) and the free pool (H), then commit (J) */ + if (restored && ftl_writable) + { + uint32_t i, lblock; + + for (lblock = 0; lblock < usersb && !rc; lblock++) + if (ftl_is_hole(lblock)) + rc = ftl_erase_sb(ftl_map[lblock]); + ftl_nholes = 0; + for (i = 0; i < ftl_cxt.freecount && !rc; i++) + rc = ftl_erase_sb(ftl_cxt.blockpool[(ftl_cxt.nextfreeidx + i) + % FTL_POOL_SIZE]); + if (!rc) + rc = ftl_commit(); + if (rc) + { + logf("ftl: restore could not be written (%d)", rc); + ftl_writable = false; + } + } + + ftl_mounted = true; + return 0; +} diff --git a/firmware/target/arm/s5l8702/ipodnano3g/ftl-target.h b/firmware/target/arm/s5l8702/ipodnano3g/ftl-target.h new file mode 100644 index 0000000000..ac98c4cc25 --- /dev/null +++ b/firmware/target/arm/s5l8702/ipodnano3g/ftl-target.h @@ -0,0 +1,60 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2009 by Michael Sparmann + * + * Interface modelled on + * firmware/target/arm/s5l8700/ipodnano2g/ftl-target.h. + * + * Copyright (C) 2026 by Andrew Rice + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#ifndef __FTL_TARGET_H__ +#define __FTL_TARGET_H__ + +#include +#include + +/* Mount Apple's FTL. nand_init() must have succeeded. Returns 0, or a + * negative number saying which stage failed: -4 if the chip's layout or + * page size is not supported, -5 if its VFL context does not fit the chip + * table row. The FTL is mounted read-only, so that writes fail and + * ftl_sync() does nothing, in the bootloader, on a chip whose row is not + * validated, and if anything a commit must reproduce could not be + * loaded. */ +int ftl_init(void); + +/* Read count logical sectors of NAND_PAGE_SIZE bytes, one or two to a NAND + * page. Returns 0 or a negative number. */ +int ftl_read(uint32_t sector, uint32_t count, void *buffer); + +/* Write count logical sectors of NAND_PAGE_SIZE bytes. The data is on the + * medium when this returns, but the block map that finds it is not: call + * ftl_sync() to commit that. Returns 0 or a negative number. */ +int ftl_write(uint32_t sector, uint32_t count, const void *buffer); + +/* Commit the block map, so the writes above survive a remount. Returns 0 or + * a negative number; after a failure the FTL refuses further writes. */ +int ftl_sync(void); + +/* Number of logical sectors, 0 if the FTL is not mounted */ +uint32_t ftl_num_sectors(void); + +/* Whether the mounted FTL refuses writes */ +bool ftl_readonly_mount(void); + +#endif /* __FTL_TARGET_H__ */ diff --git a/firmware/target/arm/s5l8702/ipodnano3g/nand-check-nano3g.c b/firmware/target/arm/s5l8702/ipodnano3g/nand-check-nano3g.c new file mode 100644 index 0000000000..9509e1c0b8 --- /dev/null +++ b/firmware/target/arm/s5l8702/ipodnano3g/nand-check-nano3g.c @@ -0,0 +1,316 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2026 by Andrew Rice + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +/* + * The contributor check image (a bootloader built with -DNAND_CHECK, run + * from DFU; utils/ipodnano3g/nandcheck/build.sh builds it). It identifies + * the chip, tries the read-only mount, and serves a raw, read-only view of + * the NAND over USB for utils/ipodnano3g/nandcheck/nandcheck.py, in + * SECTOR_SIZE sectors: + * + * 0 a text report, NUL padded + * 1 + block * banks + bank that block's spare records: per page the + * 12 spare bytes and a result word, little + * endian: -1 if the read timed out, else + * NAND_ECC_* | the most bits the + * controller corrected in a chunk << 8 | + * 0x10000 if it found the page erased + * metaend + page * secperpage page data, bank by bank + * + * Nothing here writes to the NAND. This file is built only in a NAND_CHECK + * build, where it provides the storage API the driver (nand-nano3g.c) then + * leaves out: the disk served is the raw NAND, not the FTL's. + */ + +#include "config.h" +#include "system.h" +#include "mv.h" +#include "storage.h" +#include "nand-target.h" +#include "ftl-target.h" +#include +#include +#include +#include +#include "version.h" + +#define NAND_CHECK_RECORD 16 +#define NAND_CHECK_BATCH 8 /* pages read per bank per driver call */ +/* A block's records fill one sector: every row has 128 pages per block */ +#define NAND_CHECK_PPB (SECTOR_SIZE / NAND_CHECK_RECORD) + +static uint32_t nand_check_ids[NAND_MAX_BANKS]; +static uint32_t nand_check_ext[NAND_MAX_BANKS]; /* ID bytes 5-8 */ +static int nand_check_len; /* report bytes used */ +static char nand_check_text[SECTOR_SIZE]; +static uint8_t nand_check_spares[NAND_MAX_BANKS][SECTOR_SIZE]; +static uint32_t nand_check_block = 0xffffffff; +static uint32_t nand_check_page = 0xffffffff; +static uint8_t nand_check_buf[NAND_MAX_BANKS * NAND_CHECK_BATCH + * NAND_MAX_PAGE_SIZE] STORAGE_ALIGN_ATTR; +/* The chip to serve, taken from the driver at init: without one only the + * report is served */ +static struct nand_geometry nand_check_geo; +static bool nand_check_have_chip; + +/* rc is nand_init_chip()'s result: 0, a NAND_ERR_*, or NAND_ERR_FTL plus + * ftl_init()'s result */ +void nand_check_init(int rc) +{ + const struct nand_geometry *g; + uint32_t bank; + int ftl_rc = rc <= NAND_ERR_FTL ? rc - NAND_ERR_FTL : rc ? 1 : 0; + int n; + + for (bank = 0; bank < NAND_MAX_BANKS; bank++) + if (nand_reset(bank) || nand_identify(&nand_check_ids[bank], + &nand_check_ext[bank])) + nand_check_ids[bank] = nand_check_ext[bank] = 0; + /* Serve the raw NAND of any chip the driver identified, mounted or not. + * The raw view keeps a block's records in one sector, so a chip whose + * blocks are another size is reported but not read. */ + g = nand_check_use_chip(NAND_CHECK_PPB); + nand_check_have_chip = g != NULL; + if (g) + nand_check_geo = *g; + + n = snprintf(nand_check_text, sizeof(nand_check_text), + "nano3g-nandcheck 1\n" + "version %s\n" + "ids %08lx %08lx %08lx %08lx\n" + "ext %08lx %08lx %08lx %08lx\n" + "banks %u\n" + "nand %d\n", + rbversion, (unsigned long)nand_check_ids[0], + (unsigned long)nand_check_ids[1], + (unsigned long)nand_check_ids[2], + (unsigned long)nand_check_ids[3], + (unsigned long)nand_check_ext[0], + (unsigned long)nand_check_ext[1], + (unsigned long)nand_check_ext[2], + (unsigned long)nand_check_ext[3], nand_get_bank_count(), + rc > NAND_ERR_FTL ? rc : 0); + if (!g) + n += snprintf(nand_check_text + n, sizeof(nand_check_text) - n, + "row none\n"); + else + n += snprintf(nand_check_text + n, sizeof(nand_check_text) - n, + "row %d\n" + "mode %u\n" + "pagesize %u\n" + "blocks %u\n" + "ppb %u\n" + "userblocks %u\n" + "planes %u\n" + "vflspares %u\n" + "validated %d\n" + "ftl %d\n" + "sectors %lu\n", + nand_get_chip_row(), g->mode, + g->pagesize, g->blocks, g->pagesperblock, + g->userblocks, g->planes, g->vflspares, + g->validated, ftl_rc, + (unsigned long)ftl_num_sectors()); + n += snprintf(nand_check_text + n, sizeof(nand_check_text) - n, + "verdict %s\n", + rc == NAND_ERR_UNSUPPORTED ? "chip not in the chip table" + : rc == NAND_ERR_MIXED ? "different chips on the chip enables" + : !g ? "NAND did not answer" + : ftl_rc == 0 ? (g->validated ? "OK, validated chip" + : "OK, needs validation") + : ftl_rc == -2 ? "geometry beyond FTL" + : ftl_rc == -3 ? "no VFL context" + : ftl_rc == -4 ? "layout not supported" + : ftl_rc == -5 ? "VFL does not fit row" + : "mount failed"); + nand_check_len = MIN(n, (int)sizeof(nand_check_text) - 1); +} + +const char *nand_check_report(void) +{ + return nand_check_text; +} + +void nand_check_note(const char *line) +{ + nand_check_len += snprintf(nand_check_text + nand_check_len, + sizeof(nand_check_text) - nand_check_len, + "%s\n", line); + nand_check_len = MIN(nand_check_len, (int)sizeof(nand_check_text) - 1); +} + +/* The first sector of page data: the report and every block's records */ +static uint32_t nand_check_metaend(void) +{ + return 1 + nand_check_geo.blocks * nand_check_geo.banks; +} + +static uint64_t nand_check_sector_count(void) +{ + if (!nand_check_have_chip) + return 1; + return nand_check_metaend() + + (uint64_t)nand_check_geo.banks * nand_check_geo.blocks + * nand_check_geo.pagesperblock + * (nand_check_geo.pagesize / SECTOR_SIZE); +} + +/* Every bank's spare records for one block, reading the banks in parallel */ +static void nand_check_load_spares(uint32_t block) +{ + static struct nand_read r[NAND_MAX_BANKS * NAND_CHECK_BATCH]; + static uint32_t meta[NAND_MAX_BANKS * NAND_CHECK_BATCH][NAND_META_WORDS]; + uint32_t ppb = nand_check_geo.pagesperblock, p, q, bank, i, n; + + for (p = 0; p < ppb; p += NAND_CHECK_BATCH) + { + n = 0; + for (q = p; q < p + NAND_CHECK_BATCH && q < ppb; q++) + for (bank = 0; bank < nand_check_geo.banks; bank++, n++) + { + r[n].bank = bank; + r[n].page = block * ppb + q; + r[n].buf = nand_check_buf + n * nand_check_geo.pagesize; + r[n].meta = meta[n]; + } + if (nand_read_pages(r, n)) + for (i = 0; i < n; i++) + r[i].ecc = -1; + for (i = 0; i < n; i++) + { + uint8_t *rec = nand_check_spares[r[i].bank] + + (r[i].page % ppb) * NAND_CHECK_RECORD; + int32_t ecc = r[i].ecc; + uint32_t bits; + + if (ecc >= 0) + { + for (bits = 8; bits && !(r[i].result & (1u << (bits - 1))); + bits--); + ecc |= bits << 8; + if (r[i].result & (1u << 29)) + ecc |= 0x10000; + } + + memcpy(rec, meta[i], NAND_META_WORDS * 4); + memcpy(rec + 12, &ecc, 4); + } + } + nand_check_block = block; + nand_check_page = 0xffffffff; +} + +static void nand_check_sector(uint32_t sector, uint8_t *dst) +{ + uint32_t metaend, spp, perbank, g; + + if (sector == 0) + { + memcpy(dst, nand_check_text, SECTOR_SIZE); + return; + } + if (!nand_check_have_chip) + { + memset(dst, 0, SECTOR_SIZE); + return; + } + metaend = nand_check_metaend(); + spp = nand_check_geo.pagesize / SECTOR_SIZE; + perbank = nand_check_geo.blocks * nand_check_geo.pagesperblock; + if (sector < metaend) + { + uint32_t block = (sector - 1) / nand_check_geo.banks; + + if (block != nand_check_block) + nand_check_load_spares(block); + memcpy(dst, nand_check_spares[(sector - 1) % nand_check_geo.banks], + SECTOR_SIZE); + } + else + { + g = (sector - metaend) / spp; + if (g != nand_check_page) + { + /* Data as the chip gives it, whatever the ECC verdict: the spare + * records say which pages failed */ + nand_read_page(g / perbank, g % perbank, nand_check_buf, NULL); + nand_check_page = g; + nand_check_block = 0xffffffff; + } + memcpy(dst, nand_check_buf + (sector - metaend) % spp * SECTOR_SIZE, + SECTOR_SIZE); + } +} + +/* ---- Rockbox storage API: the check's read-only raw NAND ---- */ + +int nand_read_sectors(IF_MD(int drive,) sector_t start, int incount, + void* inbuf) +{ + IF_MD((void)drive); + uint8_t *buf = inbuf; + + if (start + incount > nand_check_sector_count()) + return -1; + while (incount--) + { + nand_check_sector((uint32_t)start++, buf); + buf += SECTOR_SIZE; + } + nand_spin(); + return 0; +} + +int nand_write_sectors(IF_MD(int drive,) sector_t start, int count, + const void* outbuf) +{ + IF_MD((void)drive); + (void)start; + (void)count; + (void)outbuf; + return -1; +} + +#ifdef HAVE_STORAGE_READONLY +bool nand_readonly(IF_MD_NONVOID(int drive)) +{ + IF_MD((void)drive); + return true; +} +#endif + +int nand_event(long id, intptr_t data) +{ + (void)id; + (void)data; + return 0; +} + +#ifdef STORAGE_GET_INFO +void nand_get_info(IF_MD(int drive,) struct storage_info *info) +{ + IF_MD((void)drive); + info->sector_size = SECTOR_SIZE; + info->num_sectors = nand_check_sector_count(); + info->vendor = "Rockbox"; + info->product = "Nano 3G NAND"; + info->revision = "chk1"; +} +#endif diff --git a/firmware/target/arm/s5l8702/ipodnano3g/nand-nano3g.c b/firmware/target/arm/s5l8702/ipodnano3g/nand-nano3g.c index 3632955dc2..28766045b1 100644 --- a/firmware/target/arm/s5l8702/ipodnano3g/nand-nano3g.c +++ b/firmware/target/arm/s5l8702/ipodnano3g/nand-nano3g.c @@ -7,6 +7,7 @@ * \/ \/ \/ \/ \/ * * Copyright (C) 2009 by Michael Sparmann + * Copyright (C) 2026 by Andrew Rice * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -18,13 +19,1109 @@ * ****************************************************************************/ +/* + * NAND driver for the iPod Nano 3G (S5L8702 FMC controller). + * + * The register sequences were worked out from the BootROM and the retail + * firmware of the owner's own player: resetting a bank, waiting for it to + * go ready, and reading a page with its per-chunk ECC. nand_identify() + * follows wInd3x (freemyipod/wInd3x, pkg/exploit/wind3x_n3g.go). + * + * Limitations: + * - Only the first FMC controller is used. Banks (chip enables) 0 to + * NAND_MAX_BANKS-1 are probed at init; banks above 0 are reachable only + * because gpio_preinit() muxes all of PCON(9), which the BootROM does not. + * - Only chips validated on hardware are driven; nand_init() refuses any + * other chip (see nand_chip_table). + * - Storage goes through Apple's FTL (ftl-nano3g.c). + * + * A build with -DNAND_CHECK is the contributor check image: it serves the + * raw NAND read-only instead of the FTL's disk, so the storage API below is + * left to nand-check-nano3g.c and this file keeps only the chip. + */ + +#include "config.h" +#include "cpu.h" +#include "system.h" +#include "kernel.h" #include "mv.h" #include "storage.h" +#include "clocking-s5l8702.h" +#include "logf.h" +#include "panic.h" +#include "nand-target.h" +#include "ftl-target.h" +#include +#include + +#define NAND_CMD_READ 0x00 +#define NAND_CMD_READ2 0x30 +#define NAND_CMD_READSTATUS 0x70 +#define NAND_CMD_READID 0x90 +#define NAND_CMD_RESET 0xFF +#define NAND_CMD_PROGRAM 0x80 +#define NAND_CMD_PROGRAM_PLANE1 0x81 /* plane 1 of a two-plane program */ +#define NAND_CMD_PROGRAM_QUEUE 0x11 /* plane 0 queued, plane 1 to come */ +#define NAND_CMD_PROGRAM2 0x10 +#define NAND_CMD_ERASE 0x60 +#define NAND_CMD_ERASE2 0xD0 + +/* READ STATUS result */ +#define NAND_STATUS_FAIL (1 << 0) +#define NAND_STATUS_READY (1 << 6) + +/* The controller moves a page in units of 2KiB, each of four 512-byte + * chunks: one unit on 2KiB-page chips, two on 4KiB-page chips */ +#define NAND_UNIT_SIZE 2048 +#define NAND_CHUNK_SIZE 512 + +/* Bus timing: Apple's values, giving FMCTRL0 = 0x43803 for bank 0. Every + * legal combination reads the ID correctly, so these are not critical. */ +#define NAND_TUNK1 4 /* 3-bit field at bit 16 */ +#define NAND_TWP 3 /* 3-bit field at bit 12 */ + +/* Apple's poll helper (0x20000e50) gives up after 100000 reads. */ +#define NAND_POLL_SPINS 1000000 + +struct nand_chip_info +{ + uint32_t id; /* first four ID bytes, little endian */ + uint8_t banks; /* chip enables holding this part */ + uint8_t mode; /* Apple's layout and programming mode */ + uint16_t blocks; /* per chip enable */ + uint16_t pagesperblock; + uint16_t pagesize; /* bytes of data per page */ + uint16_t userblocks; /* per chip enable, as Whimory counts them */ + bool validated; /* read, written and remounted on hardware */ +}; + +/* Apple's own chip table in the Nano 3G firmware (osos 1.1.3: 18 rows of 44 + * bytes holding the id, chip enables, blocks per enable, pages per block, + * 512-byte sectors per page, spare bytes, timings, user blocks and mode). + * Apple picks the row matching both the id and the number of chip enables + * answering with it, and so does nand_init(): the same part can come in + * a different mode with a different bank count. + * + * The mode decides how the VFL groups physical blocks (nand_mode_planes() + * and nand_mode_layout()) and which program writes two planes at once. Only + * validated rows are driven; nand_init() refuses any other chip, except in + * the check image, which reads a chip without mounting it writable. */ +static const struct nand_chip_info nand_chip_table[] = +{ + /* Hynix: the 4GB unit this port was developed on, and the 8GB one */ + { 0xA514D3AD, 4, 8, 4096, 128, 2048, 3872, true }, + { 0xA555D5AD, 4, 8, 8192, 128, 2048, 7744, true }, + /* Micron, 2 chip enables */ + { 0xA5D5D52C, 2, 4, 8192, 128, 2048, 7744, true }, +#ifdef NAND_CHECK + /* The rest of the chips the original firmware knows. They are here for + * the check image alone, which identifies a chip and reads it to + * collect what validating it needs; a normal build does not carry them, + * so Rockbox cannot drive a chip nobody has tested. A row moves above + * this line, with its validated flag set, once someone has run the + * check and a write test on that chip. A row tagged "reported" is + * secondhand - someone's id and geometry, unconfirmed by us; one + * tagged "checked" has a check archive that mounts and replays + * correctly in the host FTL suite - real evidence, short only of the + * write test. */ + /* Micronas (ITT Intermetall, acquired by TDK 2016): 0xEC, not Samsung */ + { 0xB614D5EC, 2, 8, 4096, 128, 4096, 3872, false }, /* checked */ + { 0xB614D5EC, 4, 1, 4096, 128, 4096, 3872, false }, /* checked */ + { 0x2555D5EC, 4, 9, 8192, 128, 2048, 7744, false }, + /* Hynix */ + { 0xB614D5AD, 4, 1, 4096, 128, 4096, 3872, false }, + /* Toshiba */ + { 0xA585D598, 2, 13, 8320, 128, 2048, 7744, false }, + { 0xA585D598, 4, 13, 8320, 128, 2048, 7744, false }, + { 0xBA94D598, 2, 12, 4096, 128, 4096, 3872, false }, + { 0xBA94D598, 4, 1, 4096, 128, 4096, 3872, false }, /* checked */ + /* Intel */ + { 0xA5D5D589, 2, 4, 8192, 128, 2048, 7744, false }, /* checked */ + { 0xA5D5D589, 4, 2, 8192, 128, 2048, 7744, false }, /* checked */ + { 0x3E94D589, 2, 3, 4096, 128, 4096, 3872, false }, /* checked */ + { 0x3ED5D789, 2, 2, 8192, 128, 4096, 7744, false }, + /* Micron */ + { 0xA5D5D52C, 4, 2, 8192, 128, 2048, 7744, false }, + { 0x3E94D52C, 2, 3, 4096, 128, 4096, 3872, false }, + { 0x3ED5D72C, 2, 2, 8192, 128, 4096, 7744, false }, +#endif +}; +/* Physical blocks per bank in one VFL block: the original firmware's mode + * switch uses 1 for mode 1, 4 for mode 4, and 2 for every other mode */ +static unsigned int nand_mode_planes(unsigned int mode) +{ + return mode == 1 ? 1 : mode == 4 ? 4 : 2; +} + +/* How the planes of a VFL block are placed: a partner in the next block, + * the other half of the chip, or both */ +static unsigned int nand_mode_layout(unsigned int mode) +{ + switch (mode) + { + case 3: case 8: return NAND_LAYOUT_ADJACENT; + case 2: case 9: case 12: return NAND_LAYOUT_HALVES; + case 4: return NAND_LAYOUT_BOTH; + case 1: return NAND_LAYOUT_SINGLE; + case 13: return NAND_LAYOUT_SPLIT13; + default: return NAND_LAYOUT_UNKNOWN; + } +} + +static const struct nand_chip_info *nand_chip; +static uint32_t nand_id; /* what bank 0 answered READ ID with */ +static unsigned int nand_banks; +static bool nand_ready = false; +static long nand_last_activity_value = -1; +static struct nand_geometry nand_geo; + +const struct nand_geometry *nand_get_geometry(void) +{ + if (!nand_ready) + return NULL; + nand_geo.banks = nand_banks; + nand_geo.blocks = nand_chip->blocks; + nand_geo.pagesperblock = nand_chip->pagesperblock; + nand_geo.planes = nand_mode_planes(nand_chip->mode); + nand_geo.userblocks = nand_chip->userblocks; + /* Whimory reserves each bank's blocks beyond its user blocks, less 23 + * superblocks: 3 FTL control blocks and a pool of 20. 89 was measured + * on the 4GB unit; the rest follow the same rule. */ + nand_geo.vflspares = (nand_chip->blocks - nand_chip->userblocks) + / nand_geo.planes - 23; + /* Mode 8 is the mode whose two-plane program nand_write_pages() + * reproduces */ + nand_geo.twoplane = nand_chip->mode == 8; + nand_geo.pagesize = nand_chip->pagesize; + nand_geo.mode = nand_chip->mode; + nand_geo.layout = nand_mode_layout(nand_chip->mode); + nand_geo.validated = nand_chip->validated; +#ifdef NAND_WRITABLE_ID + /* Validation builds for testers: mount chips with this id writable, + * whatever their bank count, e.g. -DNAND_WRITABLE_ID=0xA555D5AD */ + if (nand_chip->id == NAND_WRITABLE_ID) + nand_geo.validated = true; +#endif +#ifdef NAND_TEST_READONLY + /* Test builds: behave as on a chip that is not validated */ + nand_geo.validated = false; +#endif + return &nand_geo; +} + +/* ECC state gathered over the four chunks of a page, as the firmware's + * page read does */ +struct nand_ecc_acc +{ + bool flagged; /* FMCSTAT_UNK27 was set on some chunk */ + uint32_t unk810; /* FMUNK810 after each correction, ORed */ + uint32_t transstat; /* FMTRANSSTAT after each correction, ORed */ +}; + +static void nand_touch(void) +{ + nand_last_activity_value = current_tick; +} + +static void nand_set_fmctrl0(uint32_t bank) +{ + FMCTRL0 = (NAND_TUNK1 << 16) | (NAND_TWP << 12) | FMCTRL0_UNK1 + | 1 | FMCTRL0_CE(bank); +} + +/* Spin until bit is set in reg, then write 1 to clear it. Tight and + * bounded, like Apple's poll helper: some of these flags assert briefly. */ +static int nand_wait_reg(volatile uint32_t *reg, uint32_t bit) +{ + unsigned long spins = NAND_POLL_SPINS; + while (spins--) + { + if (*reg & bit) + { + *reg = bit; + return 0; + } + } + return -1; +} + +static int nand_wait_stat(uint32_t bit) +{ + return nand_wait_reg(&FMCSTAT, bit); +} + +/* As nand_wait_stat(), but leaves the bit set */ +static int nand_wait_stat_noclear(uint32_t bit) +{ + unsigned long spins = NAND_POLL_SPINS; + while (spins--) + if (FMCSTAT & bit) + return 0; + return -1; +} + +/* Clear every FMCSTAT bit. Needed after nand_identify() and after each page + * read: stale bits let the stage waits in nand_transfer_chunk() pass before + * the data has moved, which corrupts the next read. The original + * firmware's page read ends the same way. */ +static void nand_clear_status(void) +{ + FMCSTAT = 0xffffffff; +} + +static int nand_send_cmd(uint32_t cmd) +{ + FMCMD = cmd; + return nand_wait_stat(FMCSTAT_CMDDONE); +} + +static int nand_send_addr_byte(uint32_t addr) +{ + FMANUM = 0; + FMADDR0 = addr; + FMCTRL1 = FMCTRL1_DOTRANSADDR; + return nand_wait_stat(FMCSTAT_ADDRDONE); +} + +static int nand_send_addr_page(uint32_t page) +{ + FMANUM = 4; + FMADDR0 = page << 16; + FMADDR1 = (page >> 16) & 0xFF; + FMCTRL1 = FMCTRL1_DOTRANSADDR; + return nand_wait_stat(FMCSTAT_ADDRDONE); +} + +/* Switch the NAND pins to their NAND function: Apple's 0x20001830 with + * argument 0 (first controller). It leaves the other PCON(9) pins alone. */ +static void nand_gpio_config(void) +{ + PCON(10) = (PCON(10) & 0xFFFF0000) | 0x00002222; + PCON(9) = (PCON(9) & ~0x000F000F) | 0x00020002; + PCON(8) = 0x22222222; +} + +/* Wait for the chip to go ready, as the original firmware does after every + * operation: READ STATUS, then FMCTRL1 = 0xca has the controller read + * the status byte into FMSYND0. Apple writes FMADDR2 = 1 whatever the bank. + * Returns the status byte, or -1 on a timeout. It leaves the chip in status + * output mode, so a reader must re-issue READ. */ +static int nand_read_status(void) +{ + /* The microsecond timer, not current_tick: this runs at nand_init() + * and must be bounded with interrupts off too */ + unsigned long timeout = USEC_TIMER + 200000; + unsigned long spins; + uint32_t status = 0; + + FMADDR2 = 1; + if (nand_send_cmd(NAND_CMD_READSTATUS)) + return -1; + do + { + FMCTRL1 = 0xca; + for (spins = 10000; spins; spins--) + { + status = FMSYND0; + if (status & NAND_STATUS_READY) + break; + } + } while (!(status & NAND_STATUS_READY) + && TIME_BEFORE(USEC_TIMER, timeout)); + if (!(status & NAND_STATUS_READY)) + return -1; + FMCSTAT = FMCSTAT_STATUSREADY; + FMCTRL1 = 0x20; + return status & 0xff; +} + +/* Wait for a read or reset to finish. This used to follow Apple's ROM + * (0x20009614): READ STATUS, FMCTRL1 = 0x2a, then wait for FMCSTAT bit 23. + * That bit does set (128 reads of 128, measured), but nothing in it says + * the chip is ready; nand_read_status() checks the ready bit, as the retail + * firmware does. Both take ~300 us per page read. */ +static int nand_wait_ready(void) +{ + int status = nand_read_status(); + + FMADDR2 = 0; + FMCTRL1 = 0xe0; + return status < 0 ? NAND_ERR_READY_CMD : 0; +} + +int nand_reset(uint32_t bank) +{ + clockgate_enable(CLOCKGATE_NAND, true); + clockgate_enable(CLOCKGATE_NANDECC, true); + nand_gpio_config(); + + nand_set_fmctrl0(bank); + if (nand_send_cmd(NAND_CMD_RESET)) + return NAND_ERR_RESET_CMD; + + /* Wait out tRST as well: the old ready wait could not tell when the + * chip had left reset. */ + udelay(5000); + return nand_wait_ready(); +} + +/* Read the first four ID bytes of the bank selected by the last + * nand_reset(), following wInd3x. ext, if not NULL, gets + * the next four of the eight the controller is asked for. */ +int nand_identify(uint32_t *id, uint32_t *ext) +{ + if (nand_send_cmd(NAND_CMD_READID)) + return -1; + if (nand_send_addr_byte(0)) + return -1; + + FMDNUM = 7; + FMADDR2 = 1; + FMCSTAT = FMCSTAT_TRANSDONE; /* nand_wait_ready() leaves it set */ + FMCTRL1 = FMCTRL1_DOREADDATA | FMCTRL1_CLEARWFIFO | FMCTRL1_CLEARRFIFO + | (1 << 8); + /* wInd3x leaves TRANSDONE set here; nand_clear_status() clears it */ + if (nand_wait_stat_noclear(FMCSTAT_TRANSDONE)) + return -1; + FMADDR2 = 0x100; + FMCTRL1 = FMCTRL1_CLEARWFIFO | (1 << 8) | (1 << 9); + + *id = FMFIFO; + if (ext) + *ext = FMFIFO; + nand_clear_status(); + return 0; +} + +/* Kick the controller's correction of the chunk just read and wait for it. + * The register writes Apple's 0x2000ac1c makes, in the same order. */ +static int nand_kick_chunk(uint32_t half, uint32_t sub, uint32_t upper) +{ + FMTRANSSTAT = 0x7f; + FMTRANS1 = 0x01000180; + FMTRANS0 = (1 << (sub + 8)) | (upper << 16) | (1 << (half + 8)) | 1; + return nand_wait_reg(&FMTRANSSTAT, 1 << 2); +} + +/* Move one 512-byte quarter (chunk 0..3) of the current page to dst, as + * the firmware's page read does. Stages A and B read the chunk into the + * controller, + * which sets FMCSTAT_UNK27 if it finds ECC errors and corrects them when + * kicked. Stage C then has the controller write the chunk to dst. */ +static int nand_transfer_chunk(uint32_t chunk, void *dst, + struct nand_ecc_acc *ecc) +{ + uint32_t half = chunk & 1; + uint32_t upper = (chunk >> 1) & 1; + uint32_t sub = half + 4; + + /* Stage A: 16 units */ + FMADDR6 = upper ? 0x10 : 0; + FMADDR2 = 1 << sub; + FMDNUM = 0xf; + FMCTRL1 = 0x32; + if (nand_wait_stat(FMCSTAT_TRANSDONE)) + return -1; + + /* Stage B: 512 units */ + FMDNUM = 0x1ff; + FMADDR2 = 1 << half; + FMADDR6 = 0; + FMCTRL1 = 0x22; + if (nand_wait_stat(FMCSTAT_TRANSDONE)) + return -1; + if (FMCSTAT & FMCSTAT_UNK27) + { + if (nand_kick_chunk(half, sub, upper)) + return -1; + ecc->flagged = true; + ecc->unk810 |= FMUNK810; + ecc->transstat |= FMTRANSSTAT; + } + + /* Stage C: the controller writes dst behind the cache's back */ + commit_discard_dcache_range(dst, NAND_CHUNK_SIZE); + FMDATAW0 = (uint32_t)(intptr_t)dst; + FMDATAW1 = 7; + FMCTRL0 = (FMCTRL0 & ~FMCTRL0_ENABLEDMA) | FMCTRL0_AUTOXFER | 1; + FMADDR2 = 1 << (half + 8); + FMCTRL1 = 0x1a0; + if (nand_wait_stat(FMCSTAT_UNK20)) + return -1; + discard_dcache_range(dst, NAND_CHUNK_SIZE); + return 0; +} + +int nand_read_page(uint32_t bank, uint32_t page, void *databuf, + uint32_t *meta) +{ + struct nand_ecc_acc ecc = { false, 0, 0 }; + unsigned long spins; + uint32_t chunk; + + /* That sequence only knows 2KiB pages. Larger pages take the firmware's + * read sequence, which loops over the page's units. */ + if (nand_chip->pagesize != NAND_UNIT_SIZE) + { + struct nand_read r = { bank, page, databuf, meta, 0, 0 }; + + return nand_read_pages(&r, 1) ? -1 : r.ecc; + } + + nand_set_fmctrl0(bank); + if (nand_send_cmd(NAND_CMD_READ)) + return -1; + if (nand_send_addr_page(page)) + return -1; + if (nand_send_cmd(NAND_CMD_READ2)) + return -1; + if (nand_wait_ready()) + return -1; + if (nand_send_cmd(NAND_CMD_READ)) /* back to data output */ + return -1; + + for (chunk = 0; chunk < 4; chunk++) + { + FMCSTAT = FMCSTAT_UNK27; + if (nand_transfer_chunk(chunk, + (uint8_t *)databuf + chunk * NAND_CHUNK_SIZE, &ecc)) + return -1; + } + + /* Have the controller decode the spare metadata, as the firmware does + * after its chunk loop. The result appears in FMSYND5..7. */ + FMUNK78 = 0x5140; + FMUNK7C = 2; + spins = NAND_POLL_SPINS; + while (FMUNK7C & 2) + if (!--spins) + return -1; + if (meta) + { + meta[0] = FMSYND5; + meta[1] = FMSYND6; + meta[2] = FMSYND7; + } + + nand_clear_status(); + + /* The same classification the firmware's page read returns */ + if (!ecc.flagged) + return NAND_ECC_CLEAN; + if (ecc.unk810 & 1) + return NAND_ECC_FAILED; + if (ecc.transstat & 0x20) + return NAND_ECC_STATUS6; + return NAND_ECC_CORRECTED; +} + +/* Add one chunk's correction result to the bitmap used by Apple's firmware + * read program: bit 30 is uncorrectable, and low bit n-1 says an n-bit + * correction occurred. */ +static void nand_read_ecc_result(uint32_t *result) +{ + uint32_t ecc = FMUNK810; + + if (ecc & 1) + *result |= 1u << 30; + else + { + uint32_t count = (ecc >> 16) & 0xf; + if (count) + *result |= 1u << (count - 1); + } +} + +/* Transfer one page after its NAND page load has completed. This is the + * data and ECC path of the firmware's read program: four chunks per + * 2KiB unit, the last chunk of every unit but the last going out as the + * next unit's buffer is loaded, and the page's final chunk and erased-page + * check once at the end. */ +static int nand_read_loaded_page(struct nand_read *r, struct nand_read *next, + bool primed, uint32_t *result) +{ + static const uint32_t bufsel[3] = { 0x102, 0x201, 0x102 }; + static const uint32_t correct[4] = { 0x1101, 0x11201, 0x21101, 0x31201 }; + uint32_t units = nand_chip->pagesize / NAND_UNIT_SIZE; + uint32_t unit, chunk, stat; + unsigned long spins; + + if (!primed) + { + FMCTRL1 = 0x100e0; + if (nand_send_cmd(NAND_CMD_READ)) + return -1; + + /* Prime the first 16-byte and 512-byte controller stages. */ + FMDNUM = 0xf; + FMADDR2 = 0x10; + FMADDR6 = 0; + FMCTRL1 = 0x32; + if (nand_wait_stat(FMCSTAT_TRANSDONE)) + return -1; + FMDNUM = 0x1ff; + FMADDR2 = 1; + FMCTRL1 = 0xe2; + if (nand_wait_stat(FMCSTAT_TRANSDONE)) + return -1; + + FMCTRL0 |= FMCTRL0_AUTOXFER; + FMDATAW0 = (uint32_t)(intptr_t)r->buf; + } + + for (unit = 0; unit < units; unit++) + { + if (unit) + { + /* The previous unit's last chunk, and this unit's buffer */ + FMDNUM = 0xf; + FMADDR2 = 0x10; + FMADDR6 = 0; + FMCTRL1 = 0x32; + if (nand_wait_stat(FMCSTAT_TRANSDONE)) + return -1; + FMADDR2 = 0x201; + FMDNUM = 0x1ff; + FMCTRL1 = 0xe2; + FMTRANSSTAT = 0x1ff; + FMTRANS1 = 0x180; + FMTRANS0 = correct[3]; + if (nand_wait_reg(&FMTRANSSTAT, 1)) + return -1; + nand_read_ecc_result(result); + FMCTRL1 = 0x100; + if (nand_wait_stat(FMCSTAT_UNK20)) + return -1; + FMDATAW0 = (uint32_t)(intptr_t)r->buf + unit * NAND_UNIT_SIZE; + if (nand_wait_stat(FMCSTAT_TRANSDONE)) + return -1; + } + + for (chunk = 0; chunk < 3; chunk++) + { + FMDNUM = 0xf; + FMADDR2 = 0x10; + FMADDR6 = (chunk + 1) << 4; + FMCTRL1 = 0x32; + if (nand_wait_stat(FMCSTAT_TRANSDONE)) + return -1; + FMADDR2 = bufsel[chunk]; + FMDNUM = 0x1ff; + FMCTRL1 = 0xe2; + + FMTRANSSTAT = 0x1ff; + FMTRANS1 = 0x180; + FMTRANS0 = correct[chunk]; + if (nand_wait_reg(&FMTRANSSTAT, 1)) + return -1; + nand_read_ecc_result(result); + FMCTRL1 = 0x100; + if (nand_wait_stat(FMCSTAT_UNK20) + || nand_wait_stat(FMCSTAT_TRANSDONE)) + return -1; + } + } + + stat = FMCSTAT & FMCSTAT_UNK27; + FMCSTAT = FMCSTAT_UNK27; + if (!stat) + *result |= 1u << 29; + FMTRANSSTAT = 0x1ff; + FMTRANS1 = 0x180; + FMTRANS0 = correct[3]; + + if (next) + { + FMCTRL0 = (FMCTRL0 & ~FMCTRL0_CE_MASK) | FMCTRL0_CE(next->bank); + FMADDR2 = 1; + if (nand_send_cmd(NAND_CMD_READSTATUS)) + return -1; + FMCTRL1 = 0xca; + if (nand_wait_stat(FMCSTAT_STATUSREADY)) + return -1; + FMCTRL1 = 0x20; + } + if (nand_wait_reg(&FMTRANSSTAT, 1)) + return -1; + nand_read_ecc_result(result); + + if (!next) + { + FMDATAW1 = 7; + FMADDR2 = 0x200; + FMDNUM = 0x1ff; + FMCTRL1 = 0x1e0; + if (nand_wait_stat(FMCSTAT_UNK20)) + return -1; + } + + FMUNK78 = 0x3210; + FMUNK7C = 2; + spins = NAND_POLL_SPINS; + while (FMUNK7C & 2) + if (!--spins) + return -1; + if (r->meta) + { + r->meta[0] = FMSYND5; + r->meta[1] = FMSYND6; + r->meta[2] = FMSYND7; + } + + if (next) + { + FMCTRL1 = 0x100e0; + if (nand_send_cmd(NAND_CMD_READ)) + return -1; + FMDNUM = 0xf; + FMADDR2 = 0x10; + FMADDR6 = 0; + FMCTRL1 = 0x32; + if (nand_wait_stat(FMCSTAT_TRANSDONE)) + return -1; + FMADDR2 = 0x201; + FMDNUM = 0x1ff; + FMCTRL1 = 0xe2; + FMCTRL1 = 0x100; + if (nand_wait_stat(FMCSTAT_UNK20)) + return -1; + FMDATAW0 = (uint32_t)(intptr_t)next->buf; + if (nand_wait_stat(FMCSTAT_TRANSDONE)) + return -1; + } + return 0; +} + +int nand_read_pages(struct nand_read *r, unsigned int n) +{ + unsigned int first, end, i; + uint32_t used; + int status; + + if (!nand_ready || !n) + return -1; + for (i = 0; i < n; i++) + { + if (r[i].bank >= nand_banks + || r[i].page >= (uint32_t)nand_chip->blocks + * nand_chip->pagesperblock) + return -1; + commit_discard_dcache_range(r[i].buf, nand_chip->pagesize); + r[i].ecc = NAND_ECC_FAILED; + } + + FMCTRL1 = 0x0ff3f8e0; + FMCSTAT = 0x0ff00ffe; + FMCTRL0 = (NAND_TUNK1 << 16) | (NAND_TWP << 12) | FMCTRL0_UNK1 | 1; + + /* The FMSS program consumes the largest prefix containing no repeated + * bank, starts every page load in it, transfers those pages, then repeats + * with the remainder. */ + for (first = 0; first < n; first = end) + { + used = 0; + for (end = first; end < n && !(used & (1u << r[end].bank)); end++) + used |= 1u << r[end].bank; + + for (i = first; i < end; i++) + { + FMCTRL0 = (FMCTRL0 & ~FMCTRL0_CE_MASK) | FMCTRL0_CE(r[i].bank); + if (nand_send_cmd(NAND_CMD_READ) + || nand_send_addr_page(r[i].page) + || nand_send_cmd(NAND_CMD_READ2)) + goto fail; + } + + for (i = first; i < end; i++) + { + uint32_t result = 0; + + if (i == first) + FMCTRL0 = (FMCTRL0 & ~FMCTRL0_CE_MASK) + | FMCTRL0_CE(r[i].bank); + status = i == first ? nand_read_status() : NAND_STATUS_READY; + if (status < 0 + || nand_read_loaded_page(&r[i], i + 1 < end ? &r[i + 1] : NULL, + i != first, &result)) + goto fail; + r[i].result = result; + r[i].ecc = (result & (1u << 30)) ? NAND_ECC_FAILED + : (result & 0xff) ? NAND_ECC_CORRECTED + : NAND_ECC_CLEAN; + discard_dcache_range(r[i].buf, nand_chip->pagesize); + } + FMCTRL0 &= ~FMCTRL0_AUTOXFER; + FMCTRL0 &= ~FMCTRL0_CE_MASK; + } + return 0; + +fail: + FMCTRL0 &= ~FMCTRL0_AUTOXFER; + nand_clear_status(); + return -1; +} + +/* + * Programming and erasing. The original firmware does these with programs + * that the FMC's sequencer runs in hardware; this driver makes the register + * writes those programs do for a single page or block from the ARM. Tested + * on hardware: a page written, read back and erased again. + */ + +static int nand_finish(int status) +{ + nand_clear_status(); + if (status < 0) + return -1; + return (status & NAND_STATUS_FAIL) ? NAND_OP_FAILED : 0; +} + +int nand_erase_block(uint32_t bank, uint32_t block) +{ + if (!nand_ready || bank >= nand_banks || block >= nand_chip->blocks) + return -1; + + FMCTRL1 = 0x0ff3f8e0; + FMCSTAT = 0x0ff00ffe; + nand_set_fmctrl0(bank); + if (nand_send_cmd(NAND_CMD_ERASE)) + return nand_finish(-1); + FMANUM = 2; /* row address only */ + FMADDR0 = block * nand_chip->pagesperblock; + FMCTRL1 = FMCTRL1_DOTRANSADDR; + if (nand_wait_stat(FMCSTAT_ADDRDONE)) + return nand_finish(-1); + if (nand_send_cmd(NAND_CMD_ERASE2)) + return nand_finish(-1); + return nand_finish(nand_read_status()); +} + +/* Load a page's first chunk into the controller and encode its spare + * metadata. The rest of the page follows chunk by chunk as it goes out. */ +static int nand_load_spare(const uint32_t *meta) +{ + unsigned long spins = NAND_POLL_SPINS; + + FMSYND5 = meta[0]; + FMSYND6 = meta[1]; + FMSYND7 = meta[2]; + FMUNK78 = 0x3210; + FMUNK7C = 1; + while (FMUNK7C & 1) + if (!--spins) + return -1; + return nand_wait_stat(FMCSTAT_UNK20); +} + +/* Start the ECC engine on a chunk */ +static void nand_encode(uint32_t cmd) +{ + FMTRANSSTAT = 0x1ff; + FMTRANS1 = 0x180; + FMTRANS0 = cmd; +} + +/* READ STATUS on a bank of the current run: the chip enable is switched + * without touching the rest of FMCTRL0 */ +static int nand_run_status(uint32_t bank) +{ + FMCTRL0 = (FMCTRL0 & ~FMCTRL0_CE_MASK) | FMCTRL0_CE(bank); + return nand_read_status(); +} + +/* Program a run of pages, each with its 12 bytes of spare metadata, as the + * firmware's sequencer programs do: one plane at a time, or with two_plane + * two planes per command, as the chip's mode selects. The controller loads + * each + * 512-byte chunk from memory while the previous one goes out, a 4KiB page's + * second 2KiB unit included, and loads a page's first chunk while the page + * before it finishes, so the run never + * waits for the host. A chip is not waited for until it is given its next + * page: the others program in the meantime, and every chip used is checked + * at the end. With two_plane the pages come in pairs on one bank, plane 0 + * then plane 1. On failure *failbank names the bank that reported it. */ +int nand_write_pages(const struct nand_write *w, unsigned int n, + bool two_plane, uint32_t *failbank) +{ + /* Per chunk: the ECC engine's encode command, and which of the + * controller's buffers goes out while the next chunk loads */ + static const uint32_t encode[4] = { 0x01102, 0x11202, 0x21102, 0x31202 }; + static const uint32_t bufsel[4] = { 0x102, 0x201, 0x102, 0x201 }; + uint32_t chunks = nand_chip->pagesize / NAND_CHUNK_SIZE; + uint32_t used, bank, i, chunk; + int status; + + if (!nand_ready || !n || (two_plane && (n & 1))) + return -1; + for (i = 0; i < n; i++) + { + if (w[i].bank >= nand_banks + || w[i].page >= (uint32_t)nand_chip->blocks + * nand_chip->pagesperblock) + return -1; + /* The controller reads the data behind the cache's back */ + commit_dcache_range(w[i].buf, nand_chip->pagesize); + } + + FMCTRL1 = 0x0ff3f8e0; + FMCSTAT = 0x0ff00ffe; + FMCTRL0 = (NAND_TUNK1 << 16) | (NAND_TWP << 12) | FMCTRL0_UNK1 | 1; + FMCTRL0 = (FMCTRL0 & ~FMCTRL0_CE_MASK) | FMCTRL0_CE(w[0].bank); + used = 1 << w[0].bank; + + /* Chunk 0 of the first page into the controller */ + FMDATAW0 = (uint32_t)(intptr_t)w[0].buf; + FMCTRL0 |= FMCTRL0_AUTOXFER; + FMDNUM = 0x1ff; + FMADDR2 = 1; + FMCTRL1 = 0x2e0; + if (nand_load_spare(w[0].meta)) + goto fail; + nand_encode(encode[0]); + + for (i = 0; i < n; i++) + { + bool plane1 = two_plane && (i & 1); + bool last = i + 1 == n; + + if (nand_send_cmd(plane1 ? NAND_CMD_PROGRAM_PLANE1 : NAND_CMD_PROGRAM) + || nand_send_addr_page(w[i].page) + || nand_wait_reg(&FMTRANSSTAT, 1 << 0)) + goto fail; + + for (chunk = 0; chunk < chunks; chunk++) + { + bool pageend = chunk + 1 == chunks; + + if (pageend && last) + FMCTRL0 &= ~FMCTRL0_AUTOXFER; + + /* 16 bytes of ECC and spare */ + FMDNUM = 0xf; + FMADDR2 = 0x1000; + FMADDR7 = (chunk % 4) << 4; + FMCTRL1 = 0x34; + if (pageend && !last) + FMDATAW0 = (uint32_t)(intptr_t)w[i + 1].buf; + else if (chunk % 4 == 3 && !pageend) /* the next 2KiB unit */ + FMDATAW0 = (uint32_t)(intptr_t)w[i].buf + + (chunk + 1) * NAND_CHUNK_SIZE; + if (nand_wait_stat(FMCSTAT_TRANSDONE)) + goto fail; + + /* 512 bytes of data, and the next chunk in behind them */ + if (pageend && last) + { + FMDATAW1 = 7; + FMADDR2 = 0x200; + FMDNUM = 0x1ff; + FMCTRL1 = 0xe4; + if (nand_wait_stat(FMCSTAT_TRANSDONE)) + goto fail; + break; + } + FMADDR2 = bufsel[chunk % 4]; + FMDNUM = 0x1ff; + FMCTRL1 = 0x2e4; + if (pageend) + { + if (nand_load_spare(w[i + 1].meta)) + goto fail; + nand_encode(encode[0]); + if (nand_wait_stat(FMCSTAT_TRANSDONE)) + goto fail; + break; + } + if (nand_wait_stat(FMCSTAT_UNK20)) + goto fail; + nand_encode(encode[(chunk + 1) % 4]); + if (nand_wait_reg(&FMTRANSSTAT, 1 << 0) + || nand_wait_stat(FMCSTAT_TRANSDONE)) + goto fail; + } + + /* Plane 0 of a pair is queued; anything else starts programming */ + if (nand_send_cmd(two_plane && !plane1 ? NAND_CMD_PROGRAM_QUEUE + : NAND_CMD_PROGRAM2)) + goto fail; + if (last) + break; + + /* On to the next page's chip, which must have finished whatever it + * was given before */ + bank = w[i + 1].bank; + FMCTRL0 = (FMCTRL0 & ~FMCTRL0_CE_MASK) | FMCTRL0_CE(bank); + if (used & (1 << bank)) + { + status = nand_read_status(); + if (status < 0) + goto fail; + if (status & NAND_STATUS_FAIL) + goto failed; + } + used |= 1 << bank; + } + + for (bank = 0; bank < NAND_MAX_BANKS; bank++) + { + if (!(used & (1 << bank))) + continue; + status = nand_run_status(bank); + if (status < 0) + goto fail; + if (status & NAND_STATUS_FAIL) + goto failed; + } + FMCTRL0 &= ~FMCTRL0_CE_MASK; + nand_clear_status(); + return 0; + +failed: + if (failbank) + *failbank = __builtin_ctz(FMCTRL0 & 0x1fe) - 1; + FMCTRL0 &= ~FMCTRL0_AUTOXFER; + nand_clear_status(); + return NAND_OP_FAILED; + +fail: + if (failbank) + *failbank = __builtin_ctz(FMCTRL0 & 0x1fe) - 1; + FMCTRL0 &= ~FMCTRL0_AUTOXFER; + return nand_finish(-1); +} + +int nand_write_page(uint32_t bank, uint32_t page, const void *databuf, + const uint32_t *meta) +{ + struct nand_write w = { bank, page, databuf, meta }; + + return nand_write_pages(&w, 1, false, NULL); +} + +uint32_t nand_get_id(void) +{ + return nand_id; +} + +unsigned int nand_get_bank_count(void) +{ + return nand_banks; +} + +#ifdef NAND_CHECK +/* Hooks for the contributor check image (nand-check-nano3g.c), which reads + * the raw NAND itself and serves the storage API in its place */ + +int nand_get_chip_row(void) +{ + return nand_chip ? (int)(nand_chip - nand_chip_table) : -1; +} + +const struct nand_geometry *nand_check_use_chip(unsigned int pagesperblock) +{ + /* Keep whatever was identified readable, mounted or not, but drop a + * chip whose blocks do not hold the pages the check's layout expects */ + if (nand_chip && nand_chip->pagesperblock != pagesperblock) + nand_chip = NULL; + nand_ready = nand_chip != NULL; + return nand_get_geometry(); +} +#endif /* NAND_CHECK */ + +/* ---- Rockbox storage API ---- */ + +static int nand_init_chip(void) +{ + uint32_t bank, id; + unsigned int i; + int rc; + + nand_ready = false; + nand_banks = 0; + nand_chip = NULL; + + rc = nand_reset(0); + if (rc) + { + logf("nand: reset failed (%d)", rc); + return rc; + } + if (nand_identify(&id, NULL)) + { + logf("nand: identify failed"); + return NAND_ERR_IDENTIFY; + } + nand_id = id; + + /* Further banks count if they hold the same chip, contiguously. Like + * Apple's FIL, refuse a unit where another chip enable answers with a + * different part, or where a chip follows a missing one: mounting it + * with fewer banks would read the wrong blocks. */ + nand_banks = 1; + for (bank = 1; bank < NAND_MAX_BANKS; bank++) + { + if (nand_reset(bank) || nand_identify(&id, NULL) + || !id || id == 0xffffffff) + continue; + if (id != nand_id || bank != nand_banks) + { + logf("nand: bank %lu answers %08lx", (unsigned long)bank, + (unsigned long)id); + return NAND_ERR_MIXED; + } + nand_banks++; + } + + for (i = 0; i < ARRAYLEN(nand_chip_table); i++) + if (nand_chip_table[i].id == nand_id + && nand_chip_table[i].banks == nand_banks) + nand_chip = &nand_chip_table[i]; + if (!nand_chip) + { + /* A normal build carries only the chips Rockbox has been tested on, + * so a chip missing from the table is one nobody has validated */ + logf("nand: chip %08lx x %u is not supported", (unsigned long)nand_id, + nand_banks); + return NAND_ERR_UNSUPPORTED; + } + + nand_ready = true; + nand_touch(); + +#ifndef NAND_CHECK + /* The table can still hold a row that has not been through a write test + * (a NAND_TEST_READONLY build makes every row look that way): leave that + * chip alone too. The check image reads any chip it identifies. */ + if (!nand_get_geometry()->validated) + { + logf("nand: chip %08lx x %u is not validated", + (unsigned long)nand_id, nand_banks); + nand_ready = false; + return NAND_ERR_UNSUPPORTED; + } +#endif + + rc = ftl_init(); + if (rc) + { + logf("nand: FTL mount failed (%d)", rc); + nand_ready = false; + return NAND_ERR_FTL + rc; + } + return 0; +} int nand_init(void) { - // TODO - return 0; + int rc = nand_init_chip(); + +#ifdef NAND_CHECK + /* The check image reports whatever happened and serves the raw NAND of + * any chip it identified, mounted or not, so init always succeeds */ + nand_check_init(rc); + rc = 0; +#endif + return rc; } void nand_spindown(int seconds) @@ -34,47 +1131,67 @@ void nand_spindown(int seconds) void nand_spin(void) { + nand_touch(); } #ifdef HAVE_STORAGE_FLUSH int nand_flush(void) { - return 0; + int rc; + + if (!nand_ready) + return -1; + rc = ftl_sync(); + if (rc) + panicf("Failed to unmount flash: %d", rc); + return rc; +} +#endif + +/* ---- the FTL's disk. A NAND_CHECK build serves the raw NAND instead, from + * nand-check-nano3g.c, which defines these five entry points in place of + * the ones below ---- */ +#ifndef NAND_CHECK +#ifdef HAVE_STORAGE_READONLY +bool nand_readonly(IF_MD_NONVOID(int drive)) +{ + IF_MD((void)drive); + return ftl_readonly_mount(); } #endif int nand_read_sectors(IF_MD(int drive,) sector_t start, int incount, void* inbuf) { - // TODO -#ifdef HAVE_MULTIDRIVE - (void) drive; -#endif - (void) start; - (void) incount; - (void) inbuf; - return 0; + IF_MD((void)drive); + int rc; + + if (!nand_ready) + return -1; + rc = ftl_read((uint32_t)start, incount, inbuf); + if (!rc) + nand_touch(); + return rc; } int nand_write_sectors(IF_MD(int drive,) sector_t start, int count, const void* outbuf) { - // TODO -#ifdef HAVE_MULTIDRIVE - (void) drive; -#endif - (void) start; - (void) count; - (void) outbuf; - return 0; + IF_MD((void)drive); + int rc; + + if (!nand_ready) + return -1; + rc = ftl_write((uint32_t)start, count, outbuf); + if (!rc) + nand_touch(); + return rc; } int nand_event(long id, intptr_t data) { - // TODO (void) id; (void) data; - return 0; } @@ -83,15 +1200,15 @@ void nand_get_info(IF_MD(int drive,) struct storage_info *info) { IF_MD((void)drive); info->sector_size = SECTOR_SIZE; - info->num_sectors = 0; - info->vendor = ""; - info->product = ""; - info->revision = ""; + info->num_sectors = ftl_num_sectors(); + info->vendor = "Apple"; + info->product = "iPod Nano 3G"; + info->revision = "1.0"; } #endif +#endif /* !NAND_CHECK */ long nand_last_disk_activity(void) { - // TODO - return 0; + return nand_last_activity_value; } diff --git a/firmware/target/arm/s5l8702/ipodnano3g/nand-target.h b/firmware/target/arm/s5l8702/ipodnano3g/nand-target.h new file mode 100644 index 0000000000..af9420e22b --- /dev/null +++ b/firmware/target/arm/s5l8702/ipodnano3g/nand-target.h @@ -0,0 +1,183 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2009 by Michael Sparmann + * + * Interface modelled on + * firmware/target/arm/s5l8700/ipodnano2g/nand-target.h. + * + * Copyright (C) 2026 by Andrew Rice + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#ifndef __NAND_TARGET_H__ +#define __NAND_TARGET_H__ + +#include +#include + +/* The smallest page, which is also the storage layer's sector: 4KiB-page + * chips hold two sectors per page */ +#define NAND_PAGE_SIZE 2048 +#define NAND_MAX_PAGE_SIZE 4096 + +/* The on-flash VFL suggests four banks on the 4GB model. FMCTRL0 has chip + * enable bits for eight. */ +#define NAND_MAX_BANKS 4 + +/* Non-negative results of nand_read_page(): the ECC verdict of Apple's + * the firmware's page read. Every written page reads as CORRECTED and every + * erased page + * as CLEAN (measured over a whole 4GB unit), so CORRECTED is the normal + * result for data. FAILED and STATUS6 are named from how the ROM derives + * them and have not been seen. */ +#define NAND_ECC_CLEAN 0 +#define NAND_ECC_CORRECTED 1 +#define NAND_ECC_FAILED 3 +#define NAND_ECC_STATUS6 6 + +/* Size of the spare metadata nand_read_page() returns, in 32-bit words. + * This is the 12-byte Whimory spare header (lpn/usn/type/eccmark). */ +#define NAND_META_WORDS 3 + +/* How a VFL block's planes sit on a bank (Apple's mode functions for the + * FIL, which the original firmware installs per chip mode) */ +#define NAND_LAYOUT_UNKNOWN 0 +#define NAND_LAYOUT_SINGLE 1 /* one block: mode 1 */ +#define NAND_LAYOUT_ADJACENT 2 /* block 2b + plane: modes 3, 8 */ +#define NAND_LAYOUT_HALVES 3 /* b + plane * blocks / 2: 2, 9, 12 */ +#define NAND_LAYOUT_BOTH 4 /* both of the above: mode 4 */ +#define NAND_LAYOUT_SPLIT13 5 /* Toshiba's 8320 blocks: mode 13 */ + +struct nand_geometry +{ + unsigned int banks; + unsigned int blocks; /* per bank */ + unsigned int pagesperblock; + unsigned int planes; /* physical blocks per bank in a VFL block */ + unsigned int userblocks; /* per bank, as Whimory counts them */ + unsigned int vflspares; /* VFL reserved blocks per plane */ + bool twoplane; /* whole rows may be programmed two-plane */ + unsigned int pagesize; /* bytes of data per page */ + unsigned int mode; /* Apple's chip table mode */ + unsigned int layout; /* NAND_LAYOUT_* */ + bool validated; /* may be mounted writable */ +}; + +/* Reset one bank (chip enable) and select it. Returns 0 or a NAND_ERR_*. */ +int nand_reset(uint32_t bank); + +/* Read the first four ID bytes of the bank the last nand_reset() selected. + * ext, if not NULL, gets the next four. Returns 0, or -1 on a timeout. */ +int nand_identify(uint32_t *id, uint32_t *ext); + +/* Read one page. meta may be NULL. Returns a negative number if the + * controller timed out, else one of NAND_ECC_*. */ +int nand_read_page(uint32_t bank, uint32_t page, void *databuf, + uint32_t *meta); + +/* One page of a read run. nand_read_pages() fills ecc with one of + * NAND_ECC_* when the controller completed the page. */ +struct nand_read +{ + uint32_t bank; + uint32_t page; + void *buf; + uint32_t *meta; + int ecc; + uint32_t result; /* the read program's result bitmap: bit 30 + * uncorrectable, bit 29 erased, bit n-1 an n-bit + * correction in some chunk */ +}; + +/* Read a run of pages with Apple's firmware read sequence, overlapping the + * NAND page-load time on the chip enables in the run. Returns 0 if every + * page completed or -1 on a controller timeout; inspect each page's ecc for + * its ECC verdict. */ +int nand_read_pages(struct nand_read *r, unsigned int n); + +/* Erase one block, or write one page with its NAND_META_WORDS words of spare + * metadata. Return 0, -1 if the controller timed out, or NAND_OP_FAILED if + * the chip reports a failure (the block should be treated as bad). */ +#define NAND_OP_FAILED (-2) +int nand_erase_block(uint32_t bank, uint32_t block); +int nand_write_page(uint32_t bank, uint32_t page, const void *databuf, + const uint32_t *meta); + +/* One page of a run: where it goes, its data and its spare metadata */ +struct nand_write +{ + uint32_t bank; + uint32_t page; + const void *buf; + const uint32_t *meta; +}; + +/* Program a run of pages at once, as Apple's firmware does: the chips + * program in parallel, each waited for only when it is given its next + * page, and with two_plane the pages come in pairs - plane 0 then plane 1 + * of one bank - programmed by one command each. Returns as + * nand_write_page(). After a failure *failbank (if not NULL) is the bank + * that was being programmed or reported it, and any page of the run may not + * have been written. */ +int nand_write_pages(const struct nand_write *w, unsigned int n, + bool two_plane, uint32_t *failbank); + +/* nand_init() results. -4 is retired: early builds used it for a ready + * bit timeout that turned out not to be an error. */ +enum nand_init_result +{ + NAND_ERR_RESET_CMD = -1, /* RESET never completed */ + NAND_ERR_IDENTIFY = -2, /* READ ID failed */ + NAND_ERR_READY_CMD = -3, /* READ STATUS never completed */ + NAND_ERR_UNSUPPORTED = -5, /* not a chip Rockbox has been tested on */ + NAND_ERR_MIXED = -6, /* chip enables answer with different ids */ + NAND_ERR_FTL = -100, /* plus ftl_init()'s code */ +}; + +/* The READ ID bytes bank 0 answered with, 0 before nand_init() */ +uint32_t nand_get_id(void); + +/* Number of banks (chip enables) nand_init() found holding the same chip + * as bank 0 */ +unsigned int nand_get_bank_count(void); + +/* Geometry of the attached chips, or NULL before nand_init() succeeded */ +const struct nand_geometry *nand_get_geometry(void); + +#ifdef NAND_CHECK +/* Between the driver and the contributor check image + * (nand-check-nano3g.c), which in this build serves the raw NAND as the + * storage device in the FTL disk's place */ + +/* The driver's: which row of Apple's chip table nand_init() matched, or -1 */ +int nand_get_chip_row(void); + +/* The driver's: hand the check the identified chip, so its raw pages stay + * readable whether or not the mount succeeded. A chip whose blocks do not + * hold pagesperblock pages does not suit the check's sector layout and is + * dropped. Returns the geometry to serve, or NULL if there is none. */ +const struct nand_geometry *nand_check_use_chip(unsigned int pagesperblock); + +/* The check's: build the report from nand_init()'s result */ +void nand_check_init(int rc); +/* The check's report: what was identified and how the mount went */ +const char *nand_check_report(void); +/* Add a line to the report */ +void nand_check_note(const char *line); +#endif + +#endif /* __NAND_TARGET_H__ */ diff --git a/firmware/target/arm/s5l8702/ipodnano3g/pmu-nano3g.c b/firmware/target/arm/s5l8702/ipodnano3g/pmu-nano3g.c index c7cfebf7fc..e688e01402 100644 --- a/firmware/target/arm/s5l8702/ipodnano3g/pmu-nano3g.c +++ b/firmware/target/arm/s5l8702/ipodnano3g/pmu-nano3g.c @@ -112,16 +112,26 @@ void pmu_set_cpu_voltage(bool high) #endif #if (CONFIG_RTC == RTC_NANO3G) +/* The RTC, as the original firmware uses it: + * registers 0x40..0x45 hold seconds, minutes, hours, day, month and year + * (since 2000), in binary. Bit 6 of the seconds register is set whenever + * the time is written and masked off when it is read. The buffer holds the + * six values in that order. */ void pmu_read_rtc(unsigned char* buffer) { - // TODO - (void) buffer; + pmu_read_multiple(D1671_REG_RTCSEC, 6, buffer); + buffer[0] &= ~D1671_RTCSEC_SET; } void pmu_write_rtc(unsigned char* buffer) { - // TODO - (void) buffer; + int i; + + /* One register at a time, seconds first, as the original firmware + * does */ + for (i = 0; i < 6; i++) + pmu_write(D1671_REG_RTCSEC + i, + buffer[i] | (i == 0 ? D1671_RTCSEC_SET : 0)); } #endif @@ -132,7 +142,10 @@ void pmu_set_usblimit(bool fast_charge) } /* - * ADC + * ADC, as the original firmware drives it: write the channel's + * input selection to register 0x30 with bit 3 set to start a conversion, + * wait for bit 3 to clear, and read the 10-bit result from 0x32 (bits 9..2) + * and 0x31 (bits 1..0). 0x30 idles at 0x20. */ static struct mutex pmu_adc_mutex; @@ -140,21 +153,31 @@ static struct mutex pmu_adc_mutex; unsigned short pmu_adc_raw2mv( const struct pmu_adc_channel *ch, unsigned short raw) { - // TODO - (void) ch; - (void) raw; - return 0; + return ch->offset_mv + raw * ch->span_mv / 1023; } -/* returns raw value */ +/* returns raw value, averaged over ch->samples conversions */ unsigned short pmu_read_adc(const struct pmu_adc_channel *ch) { - // TODO - int raw = 0; + unsigned int sum = 0; + int i, tries; + mutex_lock(&pmu_adc_mutex); - (void) ch; + for (i = 0; i < ch->samples; i++) + { + pmu_write(D1671_REG_ADCCTL, ch->mux | D1671_ADCCTL_START); + for (tries = 0; tries < 20; tries++) + { + udelay(50); + if (!(pmu_read(D1671_REG_ADCCTL) & D1671_ADCCTL_START)) + break; + } + sum += (pmu_read(D1671_REG_ADCHI) << 2) + | (pmu_read(D1671_REG_ADCLO) & 3); + } + pmu_write(D1671_REG_ADCCTL, D1671_ADCCTL_IDLE); mutex_unlock(&pmu_adc_mutex); - return raw; + return ch->samples ? sum / ch->samples : 0; } /* @@ -201,7 +224,8 @@ int pmu_firewire_present(void) } #endif -#if defined(BOOTLOADER) // XXX: from usb-s5l8702.c +/* XXX: from usb-s5l8702.c */ +#if defined(BOOTLOADER) && !defined(HAVE_BOOTLOADER_USB_MODE) #include "usb.h" static int usb_status = USB_EXTRACTED; @@ -339,7 +363,7 @@ void pmu_preinit(void) pmu_wr(0x16, 0x14); pmu_wr(0x15, 0x14); // TBC: Vnand = 2000 + val*50 = 3000 mV pmu_wr(0x18, 0x18); // TBC TBC TBC: Vaccy = 3200 mV ??? - pmu_wr(0x10, (pmu_rd(0x10) & 0xdb) | 0x8); // TBC: bit4 is related to NAND, LDO_0x15 on/off ??? + pmu_wr(0x10, (pmu_rd(0x10) & 0xdf) | 0x8); /* keep bit 2: NAND needs it */ // TBC: 0x30, 0x31 y 0x32 seems related to ADC (norboot) pmu_wr(0x34, 0x72); // TBC: en DA9030: TBATHIGH (0-255, TBAT high temperature threshold diff --git a/firmware/target/arm/s5l8702/ipodnano3g/pmu-target.h b/firmware/target/arm/s5l8702/ipodnano3g/pmu-target.h index 5278102ffa..f8a50ef8e5 100644 --- a/firmware/target/arm/s5l8702/ipodnano3g/pmu-target.h +++ b/firmware/target/arm/s5l8702/ipodnano3g/pmu-target.h @@ -37,6 +37,9 @@ enum d1671_regs { D1671_REG_SYSCTRLA = 0x08, D1671_REG_LEDCTL = 0x20, D1671_REG_CHCTL = 0x21, // TBC + D1671_REG_ADCCTL = 0x30, + D1671_REG_ADCLO = 0x31, + D1671_REG_ADCHI = 0x32, D1671_REG_RTCSEC = 0x40, // TBC }; @@ -87,13 +90,26 @@ enum d1671_reg_chctl { D1671_CHCTL_FASTCHRG = 0x01, /* 100/500mA USB limit */ }; +enum d1671_reg_rtcsec { + /* set when the time is written, masked off when it is read */ + D1671_RTCSEC_SET = 0x40, +}; + +enum d1671_reg_adcctl { + D1671_ADCCTL_START = 0x08, + D1671_ADCCTL_IDLE = 0x20, /* register's value at rest */ +}; + /* GPIO for external PMU interrupt */ #define GPIO_EINT_PMU 0x7b struct pmu_adc_channel { const char *name; - // TODO + uint8_t mux; /* register 0x30 value selecting the input */ + uint8_t samples; /* conversions averaged, 0 = not readable */ + unsigned short offset_mv; /* millivolts at raw 0 */ + unsigned short span_mv; /* millivolts from raw 0 to raw 1023 */ }; void pmu_preinit(void); diff --git a/firmware/target/arm/s5l8702/ipodnano3g/power-nano3g.c b/firmware/target/arm/s5l8702/ipodnano3g/power-nano3g.c index 8a957aa89a..e4e8aad8bb 100644 --- a/firmware/target/arm/s5l8702/ipodnano3g/power-nano3g.c +++ b/firmware/target/arm/s5l8702/ipodnano3g/power-nano3g.c @@ -58,9 +58,19 @@ unsigned int power_input_status(void) return status; } +/* As the original firmware decides it: the charger is + * enabled (CHCTL bits 1..6), not suspended (SYSCTRLA bit 2), and not done: + * STATUSB bits 1..2 clear. Measured: they read 0 while a 4.1 V cell + * charges from USB, so they are taken as the charge-complete indication; + * they have not been seen set. */ bool charging_state(void) { - // TODO - return false; + if (!(power_input_status() & POWER_INPUT_CHARGER)) + return false; + if (!(pmu_read(D1671_REG_CHCTL) & 0x7e)) + return false; + if (pmu_read(D1671_REG_SYSCTRLA) & 0x04) + return false; + return !(pmu_read(D1671_REG_STATUSB) & 0x06); } #endif /* CONFIG_CHARGING */ diff --git a/firmware/target/arm/s5l8702/ipodnano3g/rtc-nano3g.c b/firmware/target/arm/s5l8702/ipodnano3g/rtc-nano3g.c index 215689c725..6f79e278e4 100644 --- a/firmware/target/arm/s5l8702/ipodnano3g/rtc-nano3g.c +++ b/firmware/target/arm/s5l8702/ipodnano3g/rtc-nano3g.c @@ -24,6 +24,8 @@ #include "pmu-target.h" #include "timefuncs.h" +/* The PMU keeps seconds, minutes, hours, day, month and year (since 2000) + * in binary, and no weekday; see pmu_read_rtc() */ void rtc_init(void) { @@ -31,20 +33,16 @@ void rtc_init(void) int rtc_read_datetime(struct tm *tm) { - unsigned int i; - unsigned char buf[7]; + unsigned char buf[6]; pmu_read_rtc(buf); - for (i = 0; i < sizeof(buf); i++) - buf[i] = BCD2DEC(buf[i]); - tm->tm_sec = buf[0]; tm->tm_min = buf[1]; tm->tm_hour = buf[2]; - tm->tm_mday = buf[4]; - tm->tm_mon = buf[5] - 1; - tm->tm_year = buf[6] + 100; + tm->tm_mday = buf[3]; + tm->tm_mon = buf[4] - 1; + tm->tm_year = buf[5] + 100; tm->tm_yday = 0; /* Not implemented for now */ set_day_of_week(tm); @@ -53,19 +51,14 @@ int rtc_read_datetime(struct tm *tm) int rtc_write_datetime(const struct tm *tm) { - unsigned int i; - unsigned char buf[7]; + unsigned char buf[6]; buf[0] = tm->tm_sec; buf[1] = tm->tm_min; buf[2] = tm->tm_hour; - buf[3] = tm->tm_wday; - buf[4] = tm->tm_mday; - buf[5] = tm->tm_mon + 1; - buf[6] = tm->tm_year - 100; - - for (i = 0; i < sizeof(buf); i++) - buf[i] = DEC2BCD(buf[i]); + buf[3] = tm->tm_mday; + buf[4] = tm->tm_mon + 1; + buf[5] = tm->tm_year - 100; pmu_write_rtc(buf); return 0; diff --git a/firmware/target/arm/s5l8702/ipodnano3g/wmcodec-nano3g.c b/firmware/target/arm/s5l8702/ipodnano3g/wmcodec-nano3g.c new file mode 100644 index 0000000000..2043bfb486 --- /dev/null +++ b/firmware/target/arm/s5l8702/ipodnano3g/wmcodec-nano3g.c @@ -0,0 +1,65 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * iPod Nano 3G code for the Wolfson codec (WM1870) + * + * Copyright (c) 2010 Michael Sparmann + * Copyright (C) 2026 by Andrew Rice + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#include "system.h" +#include "audiohw.h" +#include "i2c-s5l8702.h" +#include "wmcodec.h" + +/* The PCM driver sets the codec up (audiohw_preinit()) and controls its + * MCLK */ +void audiohw_init(void) +{ +} + +/* The codec runs from a 12 MHz MCLK here, so its rate codes are the USB + * mode ones, with a BCLK divider, as the original firmware sets them. */ +unsigned short wmcodec_sampctrl(unsigned long rate) +{ + static const struct + { + unsigned long rate; + unsigned short sampctrl; + } rates[] = + { + { SAMPR_8, 0x10d }, { SAMPR_11, 0x133 }, { SAMPR_12, 0x111 }, + { SAMPR_16, 0x115 }, { SAMPR_22, 0x137 }, { SAMPR_24, 0x139 }, + { SAMPR_32, 0x119 }, { SAMPR_44, 0x123 }, { SAMPR_48, 0x081 }, + { SAMPR_88, 0x0bf }, { SAMPR_96, 0x09d }, + }; + unsigned int i; + + for (i = 0; i < ARRAYLEN(rates); i++) + if (rates[i].rate == rate) + return rates[i].sampctrl; + return 0; +} + +/* 7-bit register and 9-bit value, as two bytes to address 0x34 on I2C + * bus 0. The codec does not acknowledge reads. */ +void wmcodec_write(int reg, int data) +{ + unsigned char d = data & 0xff; + + i2c_write(0, 0x34, (reg << 1) | ((data >> 8) & 1), 1, &d); +} diff --git a/firmware/target/arm/s5l8702/lcd-s5l8702.c b/firmware/target/arm/s5l8702/lcd-s5l8702.c index 4e8e8af1e3..1c87d37da0 100644 --- a/firmware/target/arm/s5l8702/lcd-s5l8702.c +++ b/firmware/target/arm/s5l8702/lcd-s5l8702.c @@ -37,6 +37,9 @@ #include "dma-s5l8702.h" #include "lcd-s5l8702.h" #include "lcd-target.h" +#if defined(HAVE_COMPOSITE_VIDEO_OUT) && !defined(BOOTLOADER) +#include "videoout.h" +#endif // TODO TODO TODO: HAVE_LCD_ENABLE @@ -185,6 +188,10 @@ static struct dmac_ch_cfg lcd_dma_ch_cfg = /*** clocks ***/ +#if defined(HAVE_COMPOSITE_VIDEO_OUT) && !defined(BOOTLOADER) +static bool lcd_clocks_requested = true; +#endif + // TODO: In mks5lboot --mkraw put a command to specify the address of the binary, // for example --address 0x6000, it must be greater than 0x310 which would be the default address, // pass this address (0x310..128Kb) in the dfu_options flag @@ -192,12 +199,34 @@ static struct dmac_ch_cfg lcd_dma_ch_cfg = // TODO: to lcd-target.c static void lcd_target_enable_clocks(bool enable) { +#if defined(HAVE_COMPOSITE_VIDEO_OUT) && !defined(BOOTLOADER) + lcd_clocks_requested = enable; + + /* The composite mixer shares this LCD AHB gate. Keep the controller + * clocked while video output is scanning even if the backlight sleeps. */ + if (!enable && videoout_lcd_clock_required()) + return; +#endif + clockgate_enable(CLOCKGATE_LCD, enable); #ifdef IPOD_NANO4G clockgate_enable(CLOCKGATE_LCD_2, enable); #endif } +#if defined(HAVE_COMPOSITE_VIDEO_OUT) && !defined(BOOTLOADER) +void lcd_videoout_clock_acquire(void) +{ + clockgate_enable(CLOCKGATE_LCD, true); +} + +void lcd_videoout_clock_release(void) +{ + if (!lcd_clocks_requested) + clockgate_enable(CLOCKGATE_LCD, false); +} +#endif + /*** LCD controller - low level functions ***/ @@ -381,6 +410,9 @@ void lcd_update_rect(int, int, int, int) ICODE_ATTR; void lcd_update_rect(int x, int y, int width, int height) { int pixels = width * height; +#if defined(HAVE_COMPOSITE_VIDEO_OUT) && !defined(BOOTLOADER) + int original_height = height; +#endif fb_data* p = FBADDR(x,y); uint16_t* out = lcd_dblbuf[0]; @@ -411,6 +443,10 @@ void lcd_update_rect(int x, int y, int width, int height) } while (--height); } +#if defined(HAVE_COMPOSITE_VIDEO_OUT) && !defined(BOOTLOADER) + videoout_mirror_rgb565(lcd_dblbuf[0], x, y, width, + original_height, width); +#endif displaylcd_dma(pixels); } mutex_unlock(&lcd_mutex); @@ -435,8 +471,14 @@ void lcd_blit_yuv(unsigned char * const src[3], width = (width + 1) & ~1; /* ensure width is even */ +#if defined(HAVE_COMPOSITE_VIDEO_OUT) && !defined(BOOTLOADER) + int original_height = height; +#endif int pixels = width * height; uint16_t* out = lcd_dblbuf[0]; +#if defined(HAVE_COMPOSITE_VIDEO_OUT) && !defined(BOOTLOADER) + bool videoout_yuv_mirrored = false; +#endif z = stride * src_y; yuv_src[0] = src[0] + z + src_x; @@ -449,6 +491,14 @@ void lcd_blit_yuv(unsigned char * const src[3], { displaylcd_wait_dma(); +#if defined(HAVE_COMPOSITE_VIDEO_OUT) && !defined(BOOTLOADER) + /* MPEG playback already supplies planar YUV420. Send those planes + * directly to the qualified VP path instead of converting twice. */ + videoout_yuv_mirrored = videoout_mirror_yuv420( + yuv_src[0], yuv_src[1], yuv_src[2], 0, 0, stride, + x, y, width, height); +#endif + displaylcd_setup(x, y, width, height); height >>= 1; @@ -461,6 +511,12 @@ void lcd_blit_yuv(unsigned char * const src[3], out += width << 1; } while (--height); +#if defined(HAVE_COMPOSITE_VIDEO_OUT) && !defined(BOOTLOADER) + if (!videoout_yuv_mirrored) + videoout_mirror_rgb565(lcd_dblbuf[0], x, y, width, + original_height, width); +#endif + displaylcd_dma(pixels); } mutex_unlock(&lcd_mutex); @@ -525,6 +581,10 @@ void lcd_shutdown(void) #ifdef HAVE_LCD_SLEEP void lcd_sleep(void) { +#if defined(HAVE_COMPOSITE_VIDEO_OUT) && !defined(BOOTLOADER) + if (videoout_lcd_clock_required()) + return; +#endif lcd_powersave(); } diff --git a/firmware/target/arm/s5l8702/lcd-s5l8702.h b/firmware/target/arm/s5l8702/lcd-s5l8702.h index 8bed192a88..76c2a179ed 100644 --- a/firmware/target/arm/s5l8702/lcd-s5l8702.h +++ b/firmware/target/arm/s5l8702/lcd-s5l8702.h @@ -81,6 +81,11 @@ struct lcd_info_rec { void lcd_awake(void); +#if defined(HAVE_COMPOSITE_VIDEO_OUT) && !defined(BOOTLOADER) +void lcd_videoout_clock_acquire(void); +void lcd_videoout_clock_release(void); +#endif + #ifdef S5L_LCD_WITH_READID void lcd_read_display_id(int mupiface, uint8_t *lcd_id); #endif diff --git a/firmware/target/arm/s5l8702/pcm-s5l8702.c b/firmware/target/arm/s5l8702/pcm-s5l8702.c index bd110f31c4..57ad20f7c4 100644 --- a/firmware/target/arm/s5l8702/pcm-s5l8702.c +++ b/firmware/target/arm/s5l8702/pcm-s5l8702.c @@ -80,6 +80,18 @@ static struct dmac_ch_cfg dma_play_ch_cfg = { #define CHUNK_MAX_BYTES (LLI_MAX_BYTES * 1) #define WATERMARK_BYTES (PCM_WATERMARK * 4) +/* I2S0 TX setup and start command. The Nano 3G uses the values Apple's + * firmware writes for its WM1870, which is the I2S clock master; the + * ipod6g values have TXCON bits 3-4 and TXCOM bit 3 set as well, and with + * them I2S0 never takes the codec's BCLK/LRCK. */ +#ifdef IPOD_NANO3G +#define I2STXCON_SETUP 0x0b100001 +#define I2STXCOM_START 0x6 +#else +#define I2STXCON_SETUP 0xb100019 +#define I2STXCOM_START 0xe +#endif + static volatile int locked = 0; static unsigned char dblbuf[2][WATERMARK_BYTES] CACHEALIGN_ATTR; static int active_dblbuf; @@ -152,7 +164,7 @@ static void sink_dma_start(const void* addr, size_t size) sink_dma_stop(); pcm_remaining = size; - I2STXCOM = 0xe; + I2STXCOM = I2STXCOM_START; dma_play_callback((void*)addr); } @@ -162,9 +174,16 @@ static void sink_dma_start(const void* addr, size_t size) /* set the configured PCM frequency */ static void sink_set_freq(uint16_t freq) { +#ifdef HAVE_CS42L55 static uint16_t last_clkcon3l = 0; - uint16_t clkcon3l; +#else + /* Not a valid setting, so the first call always programs MCLK. The + * CS42L55 driver starts MCLK itself; the WM8975 driver does not. */ + static uint16_t last_clkcon3l = 0xffff; +#endif + uint16_t clkcon3l = 0; /* OSC0 -> 12 MHz */ +#ifdef HAVE_CS42L55 /* For unknown reasons, s5l8702 I2S controller does not synchronize * with CS42L55 at 32000 Hz. To fix it, the CODEC is configured with * a sample rate of 48000 Hz and MCLK is decreased 1/3 to 8 Mhz, @@ -175,9 +194,7 @@ static void sink_set_freq(uint16_t freq) freq = HW_FREQ_48; clkcon3l = 0x3028; /* PLL2 / 3 / 9 -> 8 MHz */ } - else { - clkcon3l = 0; /* OSC0 -> 12 MHz */ - } +#endif /* configure MCLK */ /* TODO: maybe all CLKCON management should be moved to @@ -201,7 +218,7 @@ static void sink_dma_init(void) dmac_ch_init(&dma_play_ch, &dma_play_ch_cfg); - I2STXCON = 0xb100019; + I2STXCON = I2STXCON_SETUP; I2SCLKCON = 1; audiohw_preinit(); @@ -220,6 +237,7 @@ struct pcm_sink builtin_pcm_sink = { .samprs = hw_freq_sampr, .num_samprs = HW_NUM_FREQ, .default_freq = HW_FREQ_DEFAULT, + .volume_type = PCM_NATIVE_VOLUME_TYPE, }, .ops = { .init = sink_dma_init, diff --git a/firmware/target/arm/s5l8702/system-s5l8702.c b/firmware/target/arm/s5l8702/system-s5l8702.c index a706e81491..0a6548dd8d 100644 --- a/firmware/target/arm/s5l8702/system-s5l8702.c +++ b/firmware/target/arm/s5l8702/system-s5l8702.c @@ -110,6 +110,9 @@ static const struct clocking_mode clk_modes[] = /* cdiv hdiv hprat hsdiv */ /* CClk HClk PClk SM1Clk FPS */ { 1, 2, 2, 4 }, /* 216 108 54 27 42 */ #ifdef HAVE_ADJUSTABLE_CPU_FREQ + /* Preserve the qualified intermediate step used when changing between + * the normal and boosted clocks. */ + { 2, 2, 2, 4 }, /* 108 108 54 27 */ { 4, 4, 2, 2 }, /* 54 54 27 27 21 */ #endif }; @@ -264,12 +267,6 @@ void system_exception_wait(void) while(1); } -int system_memory_guard(int newmode) -{ - (void)newmode; - return 0; -} - #ifdef HAVE_ADJUSTABLE_CPU_FREQ void set_cpu_frequency(long frequency) { diff --git a/firmware/target/arm/stm32/echoplayer/clock-echoplayer.c b/firmware/target/arm/stm32/echoplayer/clock-echoplayer.c index 59cdc7a2cb..04b469a91d 100644 --- a/firmware/target/arm/stm32/echoplayer/clock-echoplayer.c +++ b/firmware/target/arm/stm32/echoplayer/clock-echoplayer.c @@ -211,3 +211,11 @@ const struct stm32_clock i2c1_ker_clock = { .lpen_reg = ITA_RCC_APB1LLPENR, .lpen_bit = BM_RCC_APB1LENR_I2C1EN, }; + +const struct stm32_clock tim7_ker_clock = { + .frequency = TIMER_FREQ, + .en_reg = ITA_RCC_APB1LENR, + .en_bit = BM_RCC_APB1LENR_TIM7EN, + .lpen_reg = ITA_RCC_APB1LLPENR, + .lpen_bit = BM_RCC_APB1LENR_TIM7EN, +}; diff --git a/firmware/target/arm/stm32/echoplayer/clock-echoplayer.h b/firmware/target/arm/stm32/echoplayer/clock-echoplayer.h index fe296ef6bb..e2d4f0be4a 100644 --- a/firmware/target/arm/stm32/echoplayer/clock-echoplayer.h +++ b/firmware/target/arm/stm32/echoplayer/clock-echoplayer.h @@ -24,9 +24,10 @@ void echoplayer_clock_init(void) INIT_ATTR; -extern struct stm32_clock sdmmc1_ker_clock; -extern struct stm32_clock ltdc_ker_clock; -extern struct stm32_clock spi5_ker_clock; -extern struct stm32_clock i2c1_ker_clock; +extern const struct stm32_clock sdmmc1_ker_clock; +extern const struct stm32_clock ltdc_ker_clock; +extern const struct stm32_clock spi5_ker_clock; +extern const struct stm32_clock i2c1_ker_clock; +extern const struct stm32_clock tim7_ker_clock; #endif /* __CLOCK_ECHOPLAYER_H__ */ diff --git a/firmware/target/arm/stm32/echoplayer/pcm-echoplayer.c b/firmware/target/arm/stm32/echoplayer/pcm-echoplayer.c index d4944ce241..d1b4830d47 100644 --- a/firmware/target/arm/stm32/echoplayer/pcm-echoplayer.c +++ b/firmware/target/arm/stm32/echoplayer/pcm-echoplayer.c @@ -309,6 +309,7 @@ struct pcm_sink builtin_pcm_sink = { .samprs = hw_freq_sampr, .num_samprs = HW_NUM_FREQ, .default_freq = HW_FREQ_DEFAULT, + .volume_type = PCM_NATIVE_VOLUME_TYPE, }, .ops = { .init = sink_dma_init, diff --git a/firmware/target/arm/stm32/echoplayer/sdmmc-echoplayer.c b/firmware/target/arm/stm32/echoplayer/sdmmc-echoplayer.c index 61ab336841..ac4f2023e0 100644 --- a/firmware/target/arm/stm32/echoplayer/sdmmc-echoplayer.c +++ b/firmware/target/arm/stm32/echoplayer/sdmmc-echoplayer.c @@ -18,16 +18,15 @@ * ****************************************************************************/ #include "sdmmc_host.h" +#include "sdmmc_poll.h" #include "clock-echoplayer.h" #include "sdmmc-stm32h7.h" #include "gpio-stm32h7.h" #include "nvic-arm.h" #include "regs/stm32h743/sdmmc.h" -/* 300ms poll interval */ -#define SDCARD_POLL_TICKS (300 * HZ / 1000) - static struct sdmmc_host sdmmc1; +static struct sdmmc_poll sdmmc1_poll; static struct stm32h7_sdmmc_controller sdmmc1_ctl; static const struct sdmmc_controller_ops sdmmc_ops = { @@ -51,48 +50,11 @@ static const struct sdmmc_host_config sdmmc_config INITDATA_ATTR = { .is_removable = true, }; -/* - * simple SD insertion poller - */ -struct sdmmc_poll -{ - struct sdmmc_host *host; - bool is_inserted; - - bool last_state; - bool curr_state; -}; - static bool is_sdcard_inserted(void) { return gpio_get_level(GPIO_SDMMC_DETECT) == 0; } -static int poll_sdcard_inserted(struct timeout *tmo) -{ - struct sdmmc_poll *poll = (void *)tmo->data; - - poll->last_state = poll->curr_state; - poll->curr_state = is_sdcard_inserted(); - - if (!poll->curr_state && poll->is_inserted) - { - poll->is_inserted = false; - sdmmc_host_set_medium_present(poll->host, false); - } - else if (poll->curr_state && !poll->is_inserted && - poll->curr_state == poll->last_state) - { - poll->is_inserted = true; - sdmmc_host_set_medium_present(poll->host, true); - } - - return SDCARD_POLL_TICKS; -} - -static struct timeout sdcard_poll_timeout; -static struct sdmmc_poll sdcard_poll; - void sdmmc_host_target_init(void) { /* Initialize controller */ @@ -100,15 +62,13 @@ void sdmmc_host_target_init(void) stm32h7_reset_sdmmc1, NULL); nvic_enable_irq(NVIC_IRQN_SDMMC1); - /* Initialize card detect polling */ - sdcard_poll.host = &sdmmc1; - sdcard_poll.is_inserted = is_sdcard_inserted(); - timeout_register(&sdcard_poll_timeout, poll_sdcard_inserted, - SDCARD_POLL_TICKS, (intptr_t)&sdcard_poll); - /* Initialize SD/MMC host driver */ sdmmc_host_init(&sdmmc1, &sdmmc_config, &sdmmc_ops, &sdmmc1_ctl); - sdmmc_host_init_medium_present(&sdmmc1, sdcard_poll.is_inserted); + sdmmc_host_init_medium_present(&sdmmc1, is_sdcard_inserted()); + + /* Start insertion poller */ + sdmmc_poll_init(&sdmmc1_poll, &sdmmc1, is_sdcard_inserted); + sdmmc_poll_start(&sdmmc1_poll); } void sdmmc1_irq_handler(void) diff --git a/firmware/target/arm/stm32/echoplayer/system-echoplayer.c b/firmware/target/arm/stm32/echoplayer/system-echoplayer.c index 79555f7256..2a5073915c 100644 --- a/firmware/target/arm/stm32/echoplayer/system-echoplayer.c +++ b/firmware/target/arm/stm32/echoplayer/system-echoplayer.c @@ -19,13 +19,16 @@ ****************************************************************************/ #include "system.h" #include "button.h" +#include "nvic-arm.h" #include "gpio-stm32h7.h" +#include "timer-stm32h7.h" #include "clock-echoplayer.h" #include "system-echoplayer.h" #include "regs/stm32h743/fmc.h" #include "regs/stm32h743/pwr.h" #include "regs/stm32h743/rcc.h" #include "regs/stm32h743/rtc.h" +#include "regs/stm32h743/tim.h" #include "regs/cortex-m/cm_scb.h" #ifdef BOOTLOADER @@ -251,6 +254,9 @@ void system_init(void) /* Disable RTC_OUT pin */ echoplayer_set_rtcout_mode(ECHOPLAYER_RTCOUT_DISABLED); + + /* Setup for timer API */ + stm32h7_timer_init(ITA_TIM7, NVIC_IRQN_TIM7, &tim7_ker_clock); } void system_exception_wait(void) diff --git a/firmware/target/arm/stm32/echoplayer/timer-echoplayer.c b/firmware/target/arm/stm32/echoplayer/timer-echoplayer.c new file mode 100644 index 0000000000..845d83e0a6 --- /dev/null +++ b/firmware/target/arm/stm32/echoplayer/timer-echoplayer.c @@ -0,0 +1,25 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2026 Aidan MacDonald + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#include "timer-stm32h7.h" + +void tim7_irq_handler(void) +{ + stm32h7_timer_irq(); +} diff --git a/firmware/target/arm/stm32/irqhandlers-stm32h743.c b/firmware/target/arm/stm32/irqhandlers-stm32h743.c index c9a2d65d90..b40b1bdf2f 100644 --- a/firmware/target/arm/stm32/irqhandlers-stm32h743.c +++ b/firmware/target/arm/stm32/irqhandlers-stm32h743.c @@ -68,6 +68,22 @@ void spi3_irq_handler(void) ATTR_IRQ_HANDLER; void spi4_irq_handler(void) ATTR_IRQ_HANDLER; void spi5_irq_handler(void) ATTR_IRQ_HANDLER; void spi6_irq_handler(void) ATTR_IRQ_HANDLER; +void tim1_brk_irq_handler(void) ATTR_IRQ_HANDLER; +void tim1_cc_irq_handler(void) ATTR_IRQ_HANDLER; +void tim1_trg_irq_handler(void) ATTR_IRQ_HANDLER; +void tim1_up_irq_handler(void) ATTR_IRQ_HANDLER; +void tim2_irq_handler(void) ATTR_IRQ_HANDLER; +void tim3_irq_handler(void) ATTR_IRQ_HANDLER; +void tim4_irq_handler(void) ATTR_IRQ_HANDLER; +void tim6_dac_irq_handler(void) ATTR_IRQ_HANDLER; +void tim7_irq_handler(void) ATTR_IRQ_HANDLER; +void tim8_brk_tim12_irq_handler(void) ATTR_IRQ_HANDLER; +void tim8_cc_irq_handler(void) ATTR_IRQ_HANDLER; +void tim8_trg_tim14_irq_handler(void) ATTR_IRQ_HANDLER; +void tim8_up_tim13_irq_handler(void) ATTR_IRQ_HANDLER; +void tim15_irq_handler(void) ATTR_IRQ_HANDLER; +void tim16_irq_handler(void) ATTR_IRQ_HANDLER; +void tim17_irq_handler(void) ATTR_IRQ_HANDLER; void exti0_irq_handler(void) ATTR_IRQ_HANDLER; void exti1_irq_handler(void) ATTR_IRQ_HANDLER; void exti2_irq_handler(void) ATTR_IRQ_HANDLER; diff --git a/firmware/target/arm/stm32/sdmmc-stm32h7.c b/firmware/target/arm/stm32/sdmmc-stm32h7.c index cfd7fabbef..ed93f229df 100644 --- a/firmware/target/arm/stm32/sdmmc-stm32h7.c +++ b/firmware/target/arm/stm32/sdmmc-stm32h7.c @@ -57,17 +57,6 @@ #define BUSY_END_BITS \ (BUSY_SUCCESS_BITS | DATA_ERROR_BITS) -static size_t get_sdmmc_bus_freq(uint32_t clock) -{ - switch (clock) - { - case SDMMC_BUS_CLOCK_400KHZ: return 400000; - case SDMMC_BUS_CLOCK_25MHZ: return 25000000; - case SDMMC_BUS_CLOCK_50MHZ: return 50000000; - default: return 0; - } -} - static size_t abs_diff(size_t a, size_t b) { return a > b ? a - b : b - a; @@ -208,7 +197,7 @@ void stm32h7_sdmmc_set_bus_clock(void *controller, uint32_t clock) return; size_t ker_freq = stm32_clock_get_frequency(ctl->clock); - size_t bus_freq = get_sdmmc_bus_freq(clock); + size_t bus_freq = sdmmc_host_get_bus_freq(clock); if (!bus_freq) panicf("%s", __func__); @@ -389,16 +378,8 @@ int stm32h7_sdmmc_submit_command(void *controller, /* Wait for command completion */ semaphore_wait(&ctl->sem, TIMEOUT_BLOCK); - /* - * Discard data from speculative reads that may have - * accessed the buffer during the DMA transfer. - */ + /* Save original command status code */ int cmd_error = ctl->cmd_error; - if (cmd_error == SDMMC_STATUS_OK) - { - if (SDMMC_DATA_DIR(cmd->flags) == SDMMC_DATA_READ) - discard_dcache_range(buff_addr, buff_size); - } /* * If a data transfer command fails we need to issue CMD12 @@ -420,6 +401,15 @@ int stm32h7_sdmmc_submit_command(void *controller, stm32h7_sdmmc_submit_command(ctl, &cmd12, NULL); } + /* + * Discard data from speculative reads that may have + * accessed the buffer during the DMA transfer. Must + * be done after the CMD12 above otherwise the cache + * may end up out of sync with main memory. + */ + if (SDMMC_DATA_DIR(cmd->flags) == SDMMC_DATA_READ) + discard_dcache_range(buff_addr, buff_size); + /* Return error from original command */ return cmd_error; } diff --git a/firmware/target/arm/stm32/system-stm32h7.c b/firmware/target/arm/stm32/system-stm32h7.c index c1c311c18e..cbc731de4c 100644 --- a/firmware/target/arm/stm32/system-stm32h7.c +++ b/firmware/target/arm/stm32/system-stm32h7.c @@ -156,10 +156,3 @@ void udelay(uint32_t us) start = value; } } - -int system_memory_guard(int newmode) -{ - /* TODO -- maybe use MPU here to give some basic protection */ - (void)newmode; - return MEMGUARD_NONE; -} diff --git a/firmware/target/arm/stm32/timer-stm32h7.c b/firmware/target/arm/stm32/timer-stm32h7.c index 5135d9a384..a5610bf10c 100644 --- a/firmware/target/arm/stm32/timer-stm32h7.c +++ b/firmware/target/arm/stm32/timer-stm32h7.c @@ -18,19 +18,88 @@ * ****************************************************************************/ #include "timer.h" +#include "nvic-arm.h" +#include "clock-stm32h7.h" +#include "regs/stm32h743/rcc.h" +#include "regs/stm32h743/tim.h" + +static uint32_t tim_reg; +static int tim_irq; +static const struct stm32_clock *tim_clk; + +void stm32h7_timer_init(uint32_t instance, + int irq, + const struct stm32_clock *clock) +{ + tim_reg = instance; + tim_irq = irq; + tim_clk = clock; +} + +void stm32h7_timer_irq(void) +{ + reg_assignlf(tim_reg, TIM_SR, UIF(0)); + + if (pfn_timer) + pfn_timer(); +} bool timer_set(long cycles, bool start) { - (void)cycles; - (void)start; - return false; + if (cycles <= 0) + return false; + + /* Calculate timer interval */ + uint32_t counter = cycles; + uint32_t prescaler = 1; + while (counter > 0xFFFF && prescaler < 0x10000) + { + counter >>= 1; + prescaler <<= 1; + } + + /* Duration too long */ + if (counter >= 0xFFFF) + return false; + + /* Unregister old function */ + if (start && pfn_unregister) + { + pfn_unregister(); + pfn_unregister = NULL; + } + + /* Set up timer */ + stm32_clock_enable(tim_clk); + reg_writef(RCC_APB1LENR, TIM7EN(1)); + reg_assignlf(tim_reg, TIM_CR1, URS(1)); + reg_assignlf(tim_reg, TIM_EGR, UG(1)); + reg_assignlf(tim_reg, TIM_DIER, UIE(1)); + reg_assignlf(tim_reg, TIM_SR, UIF(1)); + reg_varl(tim_reg, TIM_CNT) = 0; + reg_varl(tim_reg, TIM_ARR) = counter; + reg_varl(tim_reg, TIM_PSC) = prescaler - 1; + + if (start) + return timer_start(); + + return true; } bool timer_start(void) { - return false; + stm32_clock_enable(tim_clk); + nvic_enable_irq(tim_irq); + + reg_writef(RCC_APB1LENR, TIM7EN(1)); + reg_writelf(tim_reg, TIM_CR1, URS(0), CEN(1)); + return true; } void timer_stop(void) { + nvic_disable_irq(tim_irq); + + reg_writelf(tim_reg, TIM_CR1, CEN(0)); + stm32_clock_disable(tim_clk); } diff --git a/firmware/target/arm/stm32/timer-stm32h7.h b/firmware/target/arm/stm32/timer-stm32h7.h new file mode 100644 index 0000000000..4b7527b73c --- /dev/null +++ b/firmware/target/arm/stm32/timer-stm32h7.h @@ -0,0 +1,31 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2026 Aidan MacDonald + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#ifndef __TIMER_STM32H7_H__ +#define __TIMER_STM32H7_H__ + +#include + +struct stm32_clock; + +void stm32h7_timer_init(uint32_t instance, int irq, + const struct stm32_clock *clock); +void stm32h7_timer_irq(void); + +#endif /* __TIMER_STM32H7_H__ */ diff --git a/firmware/target/arm/stm32/vectors-stm32h7.S b/firmware/target/arm/stm32/vectors-stm32h7.S index d283d808ad..9ee0b153f6 100644 --- a/firmware/target/arm/stm32/vectors-stm32h7.S +++ b/firmware/target/arm/stm32/vectors-stm32h7.S @@ -50,13 +50,13 @@ __vectors_platform: .word UIE /* [ 21] */ .word UIE /* [ 22] */ .word exti9_5_irq_handler /* [ 23] EXTI5 to EXTI9 */ - .word UIE /* [ 24] */ - .word UIE /* [ 25] */ - .word UIE /* [ 26] */ - .word UIE /* [ 27] */ - .word UIE /* [ 28] */ - .word UIE /* [ 29] */ - .word UIE /* [ 30] */ + .word tim1_brk_irq_handler /* [ 24] TIM1_BRK */ + .word tim1_up_irq_handler /* [ 25] TIM1_UP */ + .word tim1_trg_irq_handler /* [ 26] TIM1_TRG_COM */ + .word tim1_cc_irq_handler /* [ 27] TIM1_CC */ + .word tim2_irq_handler /* [ 28] TIM2 */ + .word tim3_irq_handler /* [ 29] TIM3 */ + .word tim4_irq_handler /* [ 30] TIM4 */ .word i2c1_irq_handler /* [ 31] I2C1 event */ .word i2c1_irq_handler /* [ 32] I2C1 error */ .word i2c2_irq_handler /* [ 33] I2C2 event */ @@ -69,10 +69,10 @@ __vectors_platform: .word exti15_10_irq_handler /* [ 40] EXTI10 to EXTI15 */ .word UIE /* [ 41] */ .word UIE /* [ 42] */ - .word UIE /* [ 43] */ - .word UIE /* [ 44] */ - .word UIE /* [ 45] */ - .word UIE /* [ 46] */ + .word tim8_brk_tim12_irq_handler /* [ 43] TIM8_BRK / TIM12 */ + .word tim8_up_tim13_irq_handler /* [ 44] TIM8_UP / TIM13 */ + .word tim8_trg_tim14_irq_handler /* [ 45] TIM8_TRG_COM / TIM14 */ + .word tim8_cc_irq_handler /* [ 46] TIM8_CC */ .word dma1_ch7_irq_handler /* [ 47] DMA1 Stream7 */ .word UIE /* [ 48] */ .word sdmmc1_irq_handler /* [ 49] SDMMC1 */ @@ -80,8 +80,8 @@ __vectors_platform: .word spi3_irq_handler /* [ 51] SPI3 */ .word UIE /* [ 52] */ .word UIE /* [ 53] */ - .word UIE /* [ 54] */ - .word UIE /* [ 55] */ + .word tim6_dac_irq_handler /* [ 54] TIM6 / DAC */ + .word tim7_irq_handler /* [ 55] TIM7 */ .word dma2_ch0_irq_handler /* [ 56] DMA2 Stream0 */ .word dma2_ch1_irq_handler /* [ 57] DMA2 Stream1 */ .word dma2_ch2_irq_handler /* [ 58] DMA2 Stream2 */ @@ -142,9 +142,9 @@ __vectors_platform: .word UIE /* [113] */ .word sai3_irq_handler /* [114] SAI3 */ .word UIE /* [115] */ - .word UIE /* [116] */ - .word UIE /* [117] */ - .word UIE /* [118] */ + .word tim15_irq_handler /* [116] TIM15 */ + .word tim16_irq_handler /* [117] TIM16 */ + .word tim17_irq_handler /* [118] TIM17 */ .word UIE /* [119] */ .word UIE /* [120] */ .word UIE /* [121] */ diff --git a/firmware/target/arm/tcc780x/system-tcc780x.c b/firmware/target/arm/tcc780x/system-tcc780x.c index 55b87f1db3..c049b308c6 100644 --- a/firmware/target/arm/tcc780x/system-tcc780x.c +++ b/firmware/target/arm/tcc780x/system-tcc780x.c @@ -294,12 +294,6 @@ void system_exception_wait(void) #endif } -int system_memory_guard(int newmode) -{ - (void)newmode; - return 0; -} - #ifdef HAVE_ADJUSTABLE_CPU_FREQ void set_cpu_frequency(long frequency) diff --git a/firmware/target/arm/tms320dm320/mrobe-500/pcm-mr500.c b/firmware/target/arm/tms320dm320/mrobe-500/pcm-mr500.c index 8cb966fb8a..f8c261755e 100644 --- a/firmware/target/arm/tms320dm320/mrobe-500/pcm-mr500.c +++ b/firmware/target/arm/tms320dm320/mrobe-500/pcm-mr500.c @@ -162,6 +162,7 @@ struct pcm_sink builtin_pcm_sink = { .samprs = hw_freq_sampr, .num_samprs = HW_NUM_FREQ, .default_freq = HW_FREQ_DEFAULT, + .volume_type = PCM_NATIVE_VOLUME_TYPE, }, .ops = { .init = sink_dma_init, diff --git a/firmware/target/arm/tms320dm320/sansa-connect/pcm-sansaconnect.c b/firmware/target/arm/tms320dm320/sansa-connect/pcm-sansaconnect.c index 0f529f733d..61cd143221 100644 --- a/firmware/target/arm/tms320dm320/sansa-connect/pcm-sansaconnect.c +++ b/firmware/target/arm/tms320dm320/sansa-connect/pcm-sansaconnect.c @@ -164,6 +164,7 @@ struct pcm_sink builtin_pcm_sink = { .samprs = hw_freq_sampr, .num_samprs = HW_NUM_FREQ, .default_freq = HW_FREQ_DEFAULT, + .volume_type = PCM_NATIVE_VOLUME_TYPE, }, .ops = { .init = sink_dma_init, diff --git a/firmware/target/arm/tms320dm320/sansa-connect/tnetv105_usb_drv.c b/firmware/target/arm/tms320dm320/sansa-connect/tnetv105_usb_drv.c index 6354062d85..e2874614f9 100644 --- a/firmware/target/arm/tms320dm320/sansa-connect/tnetv105_usb_drv.c +++ b/firmware/target/arm/tms320dm320/sansa-connect/tnetv105_usb_drv.c @@ -37,6 +37,17 @@ #ifdef SANSA_CONNECT #define SDRAM_SIZE 0x04000000 +static bool address_status; +static bool handle_set_address(struct usb_ctrlrequest *req); + +/* SET_ADDRESS status belongs to this driver, not the core EP0 machine. */ +static void complete_transfer(int ep, int dir, int status, int length) +{ + if(ep == EP_CONTROL && address_status) + return; + usb_core_transfer_complete(ep, dir, status, length); +} + static void set_tnetv_reset(bool high) { if (high) @@ -655,7 +666,7 @@ static void tnetv_gadget_req_nuke(int epn, bool in) tnetv_usb_reg_write(TNETV_USB_EPx_IN_CNT(epn), 0x80008000); if (ep->tx_remaining > 0) { - usb_core_transfer_complete(epn, USB_DIR_IN, -1, 0); + complete_transfer(epn, USB_DIR_IN, -1, 0); } ep->tx_buf = NULL; ep->tx_remaining = 0; @@ -674,7 +685,7 @@ static void tnetv_gadget_req_nuke(int epn, bool in) tnetv_usb_reg_write(TNETV_USB_EPx_OUT_CNT(epn), 0x80008000); if (ep->rx_remaining > 0) { - usb_core_transfer_complete(epn, USB_DIR_OUT, -1, 0); + complete_transfer(epn, USB_DIR_OUT, -1, 0); } ep->rx_buf = NULL; ep->rx_remaining = 0; @@ -865,7 +876,7 @@ static void in_interrupt(int epn) if (ep->tx_remaining <= 0) { - usb_core_transfer_complete(epn, USB_DIR_IN, 0, ep->tx_size); + complete_transfer(epn, USB_DIR_IN, 0, ep->tx_size); /* release semaphore for blocking transfer */ if (ep->block) { @@ -925,7 +936,7 @@ static void out_interrupt(int epn) is_short = rcv_len && (rcv_len < ep->max_packet_size); if (is_short || (ep->rx_remaining == 0)) { - usb_core_transfer_complete(epn, USB_DIR_OUT, 0, ep->rx_size - ep->rx_remaining); + complete_transfer(epn, USB_DIR_OUT, 0, ep->rx_size - ep->rx_remaining); ep->rx_remaining = 0; ep->rx_size = 0; ep->rx_buf = 0; @@ -955,7 +966,7 @@ static void out_interrupt(int epn) */ if ((ret == 0) && (ep->rx_remaining != ep->rx_size)) { - usb_core_transfer_complete(epn, USB_DIR_OUT, 0, ep->rx_size - ep->rx_remaining); + complete_transfer(epn, USB_DIR_OUT, 0, ep->rx_size - ep->rx_remaining); ep->rx_remaining = 0; ep->rx_size = 0; ep->rx_buf = 0; @@ -1072,7 +1083,8 @@ void VLYNQ(void) tnetv_usb_reg_write(TNETV_USB_STATUS, sysIntClear.val); tnetv_udc_handle_reset(); - usb_core_bus_reset(); + address_status = false; + usb_core_bus_reset(); } if (sysIntrStatus.f.suspend) @@ -1161,18 +1173,9 @@ void VLYNQ(void) sysIntClear.f.setup = 1; tnetv_usb_reg_write(TNETV_USB_STATUS, sysIntClear.val); - if (((setup.bRequestType & USB_RECIP_MASK) == USB_RECIP_DEVICE) && - (setup.bRequest == USB_REQ_SET_ADDRESS)) + if (usb_drv_is_set_address(&setup)) { - /* Rockbox USB core works according to USB specification, i.e. - * it first acknowledges the control transfer and then sets - * the address. However, Linux TNETV105 driver first sets the - * address and then acknowledges the transfer. At first, - * it seemed that Linux driver was wrong, but it seems that - * TNETV105 simply requires such order. It might be documented - * in the datasheet and thus there is no comment in the Linux - * driver about this. - */ + /* Defer endpoint cancellation until the driver handles the address. */ setup_is_set_address = true; } else @@ -1181,7 +1184,8 @@ void VLYNQ(void) } /* Process control packet */ - usb_core_setup_received(&setup); + if(!handle_set_address(&setup)) + usb_core_setup_received(&setup); } if (sysIntrStatus.f.ep0_in_ack) @@ -1363,7 +1367,7 @@ int usb_drv_send(int endpoint, void* ptr, int length) { if (setup_is_set_address) { - /* usb_drv_set_address() will call us later */ + /* The driver control-request handler will send the status. */ return 0; } /* HACK: Do not wait for status stage ZLP @@ -1397,19 +1401,6 @@ int usb_drv_recv_nonblocking(int endpoint, void* ptr, int length) return 0; } -void usb_drv_set_address(int address) -{ - UsbCtrlType usbCtrl; - usbCtrl.val = tnetv_usb_reg_read(TNETV_USB_CTRL); - usbCtrl.f.func_addr = address; - tnetv_usb_reg_write(TNETV_USB_CTRL, usbCtrl.val); - - /* This seems to be the only working order */ - setup_is_set_address = false; - usb_drv_send(EP_CONTROL, NULL, 0); - usb_drv_cancel_all_transfers(); -} - /* return port speed FS=0, HS=1 */ int usb_drv_port_speed(void) { @@ -1506,3 +1497,22 @@ void usb_drv_ep_deinit(const struct usb_drv_ep_alloc_ctx* ctx, int ep) int dir = EP_DIR(ep); tnetv_gadget_ep_disable(num, dir == DIR_IN); } + +static bool handle_set_address(struct usb_ctrlrequest *req) +{ + address_status = false; + if(!usb_drv_is_set_address(req)) + return false; + + const uint8_t address = req->wValue & 0x7f; + address_status = true; + UsbCtrlType usbCtrl; + usbCtrl.val = tnetv_usb_reg_read(TNETV_USB_CTRL); + usbCtrl.f.func_addr = address; + tnetv_usb_reg_write(TNETV_USB_CTRL, usbCtrl.val); + setup_is_set_address = false; + usb_drv_cancel_all_transfers(); + usb_drv_send_nonblocking(EP_CONTROL, NULL, 0); + usb_core_notify_set_address(address); + return true; +} diff --git a/firmware/target/arm/tms320dm320/system-dm320.c b/firmware/target/arm/tms320dm320/system-dm320.c index edbe14fc33..b47aa4ac16 100644 --- a/firmware/target/arm/tms320dm320/system-dm320.c +++ b/firmware/target/arm/tms320dm320/system-dm320.c @@ -393,12 +393,6 @@ void system_init(void) #endif } -int system_memory_guard(int newmode) -{ - (void)newmode; - return 0; -} - #ifdef HAVE_ADJUSTABLE_CPU_FREQ void set_cpu_frequency(long frequency) { diff --git a/firmware/target/arm/usb-drv-arc.c b/firmware/target/arm/usb-drv-arc.c index 9707ef8330..166b6eccc1 100644 --- a/firmware/target/arm/usb-drv-arc.c +++ b/firmware/target/arm/usb-drv-arc.c @@ -343,6 +343,8 @@ struct queue_head { static struct queue_head qh_array[USB_NUM_ENDPOINTS*2] USB_QHARRAY_ATTR; +static int pending_device_address = -1; + static struct semaphore transfer_completion_signal[USB_NUM_ENDPOINTS*2] SHAREDBSS_ATTR; @@ -360,7 +362,7 @@ static const unsigned int pipe2mask[USB_NUM_ENDPOINTS*2] = { /*-------------------------------------------------------------------------*/ static void transfer_completed(void); -static void control_received(void); +static void setup_received(void); static void sof_received(void); static int prime_transfer(int ep_num, void* ptr, int len, bool send, bool wait); static void prepare_td(struct transfer_descriptor* td, @@ -368,6 +370,17 @@ static void prepare_td(struct transfer_descriptor* td, static void bus_reset(void); static void init_control_queue_heads(void); /*-------------------------------------------------------------------------*/ +static bool address_status; +static bool handle_set_address(struct usb_ctrlrequest *req); + +/* SET_ADDRESS status belongs to this driver, not the core EP0 machine. */ +static void complete_transfer(int ep, int dir, int status, int length) +{ + if(ep == EP_CONTROL && address_status) + return; + usb_core_transfer_complete(ep, dir, status, length); +} + static void usb_drv_stop(void) { /* disable interrupts */ @@ -551,6 +564,16 @@ void usb_drv_int(void) unsigned int usbintr = REG_USBINTR; /* Only watch enabled ints */ unsigned int status = REG_USBSTS & usbintr; + /* Reset invalidates all old completions, including the audio ring. + * Do not dispatch a simultaneous SOF or IOC using stale descriptors. */ + if (status & USBSTS_RESET) { + REG_USBSTS = status; + bus_reset(); + address_status = false; + usb_core_bus_reset(); + return; + } + #if 0 if (status & USBSTS_INT) logf("int: usb ioc"); if (status & USBSTS_ERR) logf("int: usb err"); @@ -564,7 +587,7 @@ void usb_drv_int(void) /* a control packet? */ if (REG_ENDPTSETUPSTAT & EPSETUP_STATUS_EP0) { - control_received(); + setup_received(); } if (REG_ENDPTCOMPLETE) @@ -577,13 +600,6 @@ void usb_drv_int(void) logf("usb error int"); } - /* reset interrupt */ - if (status & USBSTS_RESET) { - REG_USBSTS = USBSTS_RESET; - bus_reset(); - usb_core_bus_reset(); /* tell mom */ - } - /* port change */ if (status & USBSTS_PORT_CHANGE) { REG_USBSTS = USBSTS_PORT_CHANGE; @@ -617,7 +633,10 @@ void usb_drv_stall(int endpoint, bool stall, bool in) REG_ENDPTCTRL(ep_num) |= EPCTRL_TX_EP_STALL; } else { - REG_ENDPTCTRL(ep_num) &= ~EPCTRL_TX_EP_STALL; + /* CLEAR_FEATURE(ENDPOINT_HALT) also resets the data toggle. + * EP0 owns its toggle through SETUP, not these register bits. */ + REG_ENDPTCTRL(ep_num) = (REG_ENDPTCTRL(ep_num) & ~EPCTRL_TX_EP_STALL) | + (ep_num ? EPCTRL_TX_DATA_TOGGLE_RST : 0); } } else { @@ -625,7 +644,8 @@ void usb_drv_stall(int endpoint, bool stall, bool in) REG_ENDPTCTRL(ep_num) |= EPCTRL_RX_EP_STALL; } else { - REG_ENDPTCTRL(ep_num) &= ~EPCTRL_RX_EP_STALL; + REG_ENDPTCTRL(ep_num) = (REG_ENDPTCTRL(ep_num) & ~EPCTRL_RX_EP_STALL) | + (ep_num ? EPCTRL_RX_DATA_TOGGLE_RST : 0); } } } @@ -675,11 +695,6 @@ bool usb_drv_powered(void) return (REG_OTGSC & OTGSC_A_VBUS_VALID) ? true : false; } -void usb_drv_set_address(int address) -{ - REG_DEVICEADDR = address << USBDEVICEADDRESS_BIT_POS; -} - void usb_drv_reset_endpoint(int endpoint, bool send) { int pipe = EP_NUM(endpoint) * 2 + (send ? 1 : 0); @@ -1060,7 +1075,7 @@ static void prepare_td(struct transfer_descriptor* td, } } -static void control_received(void) +static void setup_received(void) { int i; /* copy setup data from packet */ @@ -1071,6 +1086,13 @@ static void control_received(void) /* acknowledge packet recieved */ REG_ENDPTSETUPSTAT = EPSETUP_STATUS_EP0; + /* A new SETUP cancels both halves of the old control transfer. Flush + * only EP0: cancelling storage/HID here would lose unrelated traffic. + * Discard its completion bits before the core primes the new request. */ + usb_drv_reset_endpoint(EP_CONTROL, false); + usb_drv_reset_endpoint(EP_CONTROL, true); + REG_ENDPTCOMPLETE = pipe2mask[0] | pipe2mask[1]; + /* Stop pending control transfers */ for(i=0;i<2;i++) { if(qh_array[i].wait) { @@ -1080,7 +1102,15 @@ static void control_received(void) } } - usb_core_setup_received((struct usb_ctrlrequest*)tmp); + usb_core_control_cancelled(); + + struct usb_ctrlrequest *req = (struct usb_ctrlrequest*)tmp; + + /* A new setup packet supersedes any unfinished SET_ADDRESS request. */ + pending_device_address = -1; + + if(!handle_set_address(req)) + usb_core_setup_received(req); } static void transfer_completed(void) @@ -1113,12 +1143,21 @@ static void transfer_completed(void) ((td->size_ioc_sts & DTD_PACKET_SIZE) >> DTD_LENGTH_BIT_POS)); td=(struct transfer_descriptor*) td->next_td_ptr; } + if (ep == EP_CONTROL && dir == DIR_IN && + pending_device_address >= 0 && + qh->status == 0 && length == 0) { + REG_DEVICEADDR = + pending_device_address << USBDEVICEADDRESS_BIT_POS; + usb_core_notify_set_address(pending_device_address); + pending_device_address = -1; + } + if(qh->wait) { qh->wait=0; semaphore_release(&transfer_completion_signal[pipe]); } - usb_core_transfer_complete(ep, dir?USB_DIR_IN:USB_DIR_OUT, + complete_transfer(ep, dir?USB_DIR_IN:USB_DIR_OUT, qh->status, length); Lskip: continue; @@ -1133,7 +1172,11 @@ static void bus_reset(void) int i; logf("usb bus_reset"); + REG_USBINTR &= ~USBINTR_SOF_EN; + batch_stopped = true; + REG_DEVICEADDR = 0; + pending_device_address = -1; REG_ENDPTSETUPSTAT = REG_ENDPTSETUPSTAT; REG_ENDPTCOMPLETE = REG_ENDPTCOMPLETE; @@ -1170,3 +1213,19 @@ static void init_control_queue_heads(void) qh_array[EP_CONTROL+1].max_pkt_length = 64 << QH_MAX_PKT_LEN_POS | QH_ZLT_SEL; qh_array[EP_CONTROL+1].dtd.next_td_ptr = QH_NEXT_TERMINATE; } + +static bool handle_set_address(struct usb_ctrlrequest *req) +{ + address_status = false; + if(!usb_drv_is_set_address(req)) + return false; + + const uint8_t address = req->wValue & 0x7f; + address_status = true; + /* Capture before sending; the completion IRQ writes DEVICEADDR and + * notifies the core after the successful zero-length status packet. */ + pending_device_address = address; + usb_drv_cancel_all_transfers(); + usb_drv_send_nonblocking(EP_CONTROL, NULL, 0); + return true; +} diff --git a/firmware/target/arm/usb-s3c6400x.c b/firmware/target/arm/usb-s3c6400x.c index 79191eec35..99d84f63c1 100644 --- a/firmware/target/arm/usb-s3c6400x.c +++ b/firmware/target/arm/usb-s3c6400x.c @@ -38,9 +38,21 @@ //#define LOGF_ENABLE #include "logf.h" +static bool address_status; +static bool handle_set_address(struct usb_ctrlrequest *req); + +/* SET_ADDRESS status belongs to this driver, not the core EP0 machine. */ +static void complete_transfer(int ep, int dir, int status, int length) +{ + if(ep == EP_CONTROL && address_status) + return; + usb_core_transfer_complete(ep, dir, status, length); +} + #if CONFIG_CPU == AS3525v2 #define UNCACHED_ADDR AS3525_UNCACHED_ADDR #define PHYSICAL_ADDR AS3525_PHYSICAL_ADDR + static inline void discard_dma_buffer_cache(void) {} #elif CONFIG_CPU == S5L8702 #define UNCACHED_ADDR S5L8702_UNCACHED_ADDR @@ -125,15 +137,6 @@ void usb_drv_stall(int endpoint, bool stall, bool in) DEPCTL(endpoint, !in) &= ~DEPCTL_stall; } -void usb_drv_set_address(int address) -{ - (void)address; - /* Ignored intentionally, because the controller requires us to set the - new address before sending the response for some reason. So we'll - already set it when the control request arrives, before passing that - into the USB core, which will then call this dummy function. */ -} - static void ep_transfer(int ep, void *ptr, int len, bool out) { /* disable interrupts to avoid any race */ @@ -489,7 +492,7 @@ static void handle_ep_int(int ep, bool out) } if (!out) endpoint->size = size; - usb_core_transfer_complete(ep, out ? USB_DIR_OUT : USB_DIR_IN, 0, transfered); + complete_transfer(ep, out ? USB_DIR_OUT : USB_DIR_IN, 0, transfered); endpoint->done = true; semaphore_release(&endpoint->complete); } @@ -525,11 +528,11 @@ static void handle_ep_int(int ep, bool out) logf(" rt=%x r=%x", ep0_setup_pkt->bRequestType, ep0_setup_pkt->bRequest); - if(ep0_setup_pkt->bRequestType == USB_TYPE_STANDARD && - ep0_setup_pkt->bRequest == USB_REQ_SET_ADDRESS) - DCFG = (DCFG & ~bitm(DCFG, devadr)) | (ep0_setup_pkt->wValue << DCFG_devadr_bitp); + if(usb_drv_is_set_address(ep0_setup_pkt)) + DCFG = (DCFG & ~bitm(DCFG, devadr)) | ((ep0_setup_pkt->wValue & 0x7f) << DCFG_devadr_bitp); - usb_core_setup_received(ep0_setup_pkt); + if(!handle_set_address(ep0_setup_pkt)) + usb_core_setup_received(ep0_setup_pkt); } } @@ -547,6 +550,7 @@ void INT_USB_FUNC(void) { DCFG &= ~bitm(DCFG, devadr); /* Address 0 */ reset_endpoints(); + address_status = false; usb_core_bus_reset(); } @@ -622,3 +626,17 @@ int usb_drv_send(int ep, void *ptr, int len) semaphore_wait(&endpoint->complete, HZ); return endpoint->status; } + +static bool handle_set_address(struct usb_ctrlrequest *req) +{ + address_status = false; + if(!usb_drv_is_set_address(req)) + return false; + + const uint8_t address = req->wValue & 0x7f; + address_status = true; + usb_drv_cancel_all_transfers(); + usb_drv_send_nonblocking(EP_CONTROL, NULL, 0); + usb_core_notify_set_address(address); + return true; +} diff --git a/firmware/target/arm/usb-tcc.c b/firmware/target/arm/usb-tcc.c index 694b38f225..087ff60412 100644 --- a/firmware/target/arm/usb-tcc.c +++ b/firmware/target/arm/usb-tcc.c @@ -99,6 +99,17 @@ static struct tcc_ep tcc_endpoints[] = { static bool usb_drv_write_ep(struct tcc_ep *ep); static void usb_set_speed(int); +static bool address_status; +static bool handle_set_address(struct usb_ctrlrequest *req); + +/* SET_ADDRESS status belongs to this driver, not the core EP0 machine. */ +static void complete_transfer(int ep, int dir, int status, int length) +{ + if(ep == EP_CONTROL && address_status) + return; + usb_core_transfer_complete(ep, dir, status, length); +} + void usb_drv_ep_init(const struct usb_drv_ep_alloc_ctx* ctx, int ep) { /* FIXME: support max packet size override */ @@ -228,7 +239,8 @@ void handle_control(void) DEBUG(2, "req: %02x %02d", req->bRequestType, req->bRequest); } - usb_core_setup_received(req); + if(!handle_set_address(req)) + usb_core_setup_received(req); } static @@ -280,7 +292,7 @@ void handle_ep_in(struct tcc_ep *tcc_ep, uint16_t stat) TCC7xx_USB_EP_CTRL |= TCC7xx_USB_EP_CTRL_OUTHD; - usb_core_transfer_complete(tcc_ep->id, USB_DIR_OUT, 0, count); + complete_transfer(tcc_ep->id, USB_DIR_OUT, 0, count); } static @@ -306,7 +318,7 @@ void handle_ep_out(struct tcc_ep *tcc_ep, uint16_t stat) TCC7xx_USB_EPIE &= ~tcc_ep->mask; global_ep_irq_mask &= ~tcc_ep->mask; -// usb_core_transfer_complete(tcc_ep->id, USB_DIR_IN, 0, tcc_ep->count); +// complete_transfer(tcc_ep->id, USB_DIR_IN, 0, tcc_ep->count); } } @@ -408,7 +420,8 @@ static void usb_reset(void) global_ep_irq_mask = 0x1; TCC7xx_USB_EPIE = global_ep_irq_mask; - usb_core_bus_reset(); + address_status = false; + usb_core_bus_reset(); } /* IRQ handler */ @@ -462,12 +475,6 @@ void USB_DEVICE(void) TCC7xx_USB_INDEX = index_save; } -void usb_drv_set_address(int address) -{ - (void) address; - DEBUG(2, "setting address %d %d", address, TCC7xx_USB_FUNC); -} - int usb_drv_port_speed(void) { return (TCC7xx_USB_SYS_STAT & 0x10) ? 1 : 0; @@ -504,7 +511,7 @@ static bool usb_drv_write_ep(struct tcc_ep *ep) ep->max_len -= count; if (ep->max_len == 0) { - usb_core_transfer_complete(ep->id, USB_DIR_IN, 0, ep->count); + complete_transfer(ep->id, USB_DIR_IN, 0, ep->count); ep->buf = NULL; // return true; } @@ -632,7 +639,7 @@ void usb_drv_cancel_all_transfers(void) flags = disable_irq_save(); for (endpoint = 0; endpoint < 4; endpoint++) { if (tcc_endpoints[endpoint].buf) { -/* usb_core_transfer_complete(tcc_endpoints[endpoint].id, +/* complete_transfer(tcc_endpoints[endpoint].id, tcc_endpoints[endpoint].dir, -1, 0); */ tcc_endpoints[endpoint].buf = NULL; } @@ -755,6 +762,20 @@ void usb_test(void) } } #endif +static bool handle_set_address(struct usb_ctrlrequest *req) +{ + address_status = false; + if(!usb_drv_is_set_address(req)) + return false; + + const uint8_t address = req->wValue & 0x7f; + address_status = true; + usb_drv_cancel_all_transfers(); + usb_drv_send_nonblocking(EP_CONTROL, NULL, 0); + usb_core_notify_set_address(address); + return true; +} + #else void usb_init_device(void) { diff --git a/firmware/target/coldfire/iriver/system-iriver.c b/firmware/target/coldfire/iriver/system-iriver.c index d16931ac1f..da709e5c38 100644 --- a/firmware/target/coldfire/iriver/system-iriver.c +++ b/firmware/target/coldfire/iriver/system-iriver.c @@ -21,9 +21,11 @@ #include "cpu.h" #include "kernel.h" #include "system.h" +#include "system-iriver.h" #include "power.h" #include "timer.h" #include "pcf50606.h" +#include /* Settings for all possible clock frequencies (with properly working timers) * NOTE: Some 5249 chips don't like having PLLDIV set to 0. We must avoid that! @@ -161,3 +163,31 @@ void cf_set_cpu_frequency(long frequency) break; } } + +static bool detect_flash_header(uint8_t *addr) +{ +#ifndef BOOTLOADER + int oldmode = system_memory_guard(MEMGUARD_NONE); +#endif + struct flash_header hdr; + memcpy(&hdr, addr, sizeof(struct flash_header)); +#ifndef BOOTLOADER + system_memory_guard(oldmode); +#endif + return hdr.magic == FLASH_MAGIC; +} + +bool detect_flashed_romimage(void) +{ + return detect_flash_header((uint8_t *)FLASH_ROMIMAGE_ENTRY); +} + +bool detect_flashed_ramimage(void) +{ + return detect_flash_header((uint8_t *)FLASH_RAMIMAGE_ENTRY); +} + +bool detect_original_firmware(void) +{ + return !(detect_flashed_ramimage() || detect_flashed_romimage()); +} diff --git a/firmware/target/coldfire/iriver/system-iriver.h b/firmware/target/coldfire/iriver/system-iriver.h new file mode 100644 index 0000000000..e87a108c35 --- /dev/null +++ b/firmware/target/coldfire/iriver/system-iriver.h @@ -0,0 +1,37 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2007 Miika Pekkarinen + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#ifndef __SYSTEM_IRIVER_H__ +#define __SYSTEM_IRIVER_H__ + +#include +#include + +struct flash_header +{ + uint32_t magic; + uint32_t length; + char version[32]; +}; + +bool detect_flashed_romimage(void); +bool detect_flashed_ramimage(void); +bool detect_original_firmware(void); + +#endif /* __SYSTEM_IRIVER_H__ */ diff --git a/firmware/target/coldfire/pcm-coldfire.c b/firmware/target/coldfire/pcm-coldfire.c index 531feeec2d..24ef8ee190 100644 --- a/firmware/target/coldfire/pcm-coldfire.c +++ b/firmware/target/coldfire/pcm-coldfire.c @@ -306,6 +306,7 @@ struct pcm_sink builtin_pcm_sink = { .samprs = hw_freq_sampr, .num_samprs = HW_NUM_FREQ, .default_freq = HW_FREQ_DEFAULT, + .volume_type = PCM_NATIVE_VOLUME_TYPE, }, .ops = { .init = sink_dma_init, diff --git a/firmware/target/hosted/android/android-codec.c b/firmware/target/hosted/android/android-codec.c index 6e069210df..794ad947ed 100644 --- a/firmware/target/hosted/android/android-codec.c +++ b/firmware/target/hosted/android/android-codec.c @@ -28,11 +28,6 @@ void audiohw_set_volume(int volume) pcm_set_mixer_volume(volume); } -void audiohw_set_balance(int balance) -{ - (void)balance; -} - void audiohw_close(void) { pcm_shutdown(); diff --git a/firmware/target/hosted/android/pcm-android.c b/firmware/target/hosted/android/pcm-android.c index 467f60e6cb..ca53d9a27d 100644 --- a/firmware/target/hosted/android/pcm-android.c +++ b/firmware/target/hosted/android/pcm-android.c @@ -29,6 +29,7 @@ #include "pcm-internal.h" #include "pcm_sampr.h" #include "pcm_sink.h" +#include "audiohw.h" extern JNIEnv *env_ptr; @@ -220,6 +221,7 @@ struct pcm_sink builtin_pcm_sink = { .samprs = hw_freq_sampr, .num_samprs = HW_NUM_FREQ, .default_freq = HW_FREQ_DEFAULT, + .volume_type = PCM_NATIVE_VOLUME_TYPE, }, .ops = { .init = sink_dma_init, diff --git a/firmware/target/hosted/ctru/timer-ctru_sys.c b/firmware/target/hosted/ctru/timer-ctru_sys.c deleted file mode 100644 index 2b40fa1343..0000000000 --- a/firmware/target/hosted/ctru/timer-ctru_sys.c +++ /dev/null @@ -1,60 +0,0 @@ -/*************************************************************************** -* __________ __ ___. -* Open \______ \ ____ ____ | | _\_ |__ _______ ___ -* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / -* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < -* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ -* \/ \/ \/ \/ \/ -* -* Copyright (C) 2025 Mauricio G. -* -* This program is free software; you can redistribute it and/or -* modify it under the terms of the GNU General Public License -* as published by the Free Software Foundation; either version 2 -* of the License, or (at your option) any later version. -* -* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY -* KIND, either express or implied. -* -****************************************************************************/ - -#include "sys_timer.h" -#include "timer.h" - -static int timer_prio = -1; -void (*global_timer_callback)(void); -int timerId; - -u32 _timer_callback(u32 interval, void *param){ - (void)param; - global_timer_callback(); - return(interval); -} - -#define cycles_to_miliseconds(cycles) \ - ((int)((1000*cycles)/TIMER_FREQ)) - -bool timer_register(int reg_prio, void (*unregister_callback)(void), - long cycles, void (*timer_callback)(void)) -{ - (void)unregister_callback; - if (reg_prio <= timer_prio || cycles == 0) - return false; - timer_prio=reg_prio; - global_timer_callback=timer_callback; - timerId=sys_add_timer(cycles_to_miliseconds(cycles), _timer_callback, 0); - return true; -} - -bool timer_set_period(long cycles) -{ - sys_remove_timer(timerId); - timerId=sys_add_timer(cycles_to_miliseconds(cycles), _timer_callback, 0); - return true; -} - -void timer_unregister(void) -{ - sys_remove_timer(timerId); - timer_prio = -1; -} diff --git a/firmware/target/hosted/filesystem-app.c b/firmware/target/hosted/filesystem-app.c index 03a7bf7f18..4b0c60d962 100644 --- a/firmware/target/hosted/filesystem-app.c +++ b/firmware/target/hosted/filesystem-app.c @@ -504,7 +504,7 @@ bool app_dir_exists(const char *dirname) return true; } -struct dirinfo dir_get_info(DIR *dirp, struct dirent *entry) +struct dirinfo app_dir_get_info(DIR *dirp, struct dirent *entry) { struct __dir *this = (struct __dir *)dirp; struct dirinfo ret = { .attribute = 0, diff --git a/firmware/target/hosted/filesystem-app.h b/firmware/target/hosted/filesystem-app.h index 423e723dd1..d03dadeb01 100644 --- a/firmware/target/hosted/filesystem-app.h +++ b/firmware/target/hosted/filesystem-app.h @@ -57,11 +57,7 @@ const char * handle_special_dirs(const char *dir, unsigned flags, #ifdef WIN32 #include "filesystem-win32.h" #else /* !WIN32 */ -#if defined(CTRU) && !defined(SIMULATOR) -#include "filesystem-ctru.h" -#else #include "filesystem-unix.h" -#endif #endif /* WIN32 */ #include "filesystem-hosted.h" @@ -90,7 +86,7 @@ ssize_t app_write(int fildes, const void *buf, size_t nbyte); int app_remove(const char *path); int app_rename(const char *old, const char *new); #define app_modtime os_modtime -#define app_filesize os_filesize +#define app_ffilesize os_ffilesize #define app_fsamefile os_fsamefile int app_relate(const char *path1, const char *path2); bool app_file_exists(const char *path); @@ -119,6 +115,8 @@ int app_mkdir(const char *path); int app_rmdir(const char *path); int app_samedir(DIR *dirp1, DIR *dirp2); bool app_dir_exists(const char *dirname); +struct dirinfo app_dir_get_info(DIR *dirp, struct dirent *entry); +const char * app_root_realpath(void); #endif /* DIRFUNCTIONS_DECLARED */ #endif /* _FILESYSTEM_APP__DIR_H_ */ diff --git a/firmware/target/hosted/ctru/filesystem-ctru.h b/firmware/target/hosted/filesystem-console.h similarity index 53% rename from firmware/target/hosted/ctru/filesystem-ctru.h rename to firmware/target/hosted/filesystem-console.h index 086af83a67..9ad880f259 100644 --- a/firmware/target/hosted/ctru/filesystem-ctru.h +++ b/firmware/target/hosted/filesystem-console.h @@ -17,8 +17,8 @@ * KIND, either express or implied. * ****************************************************************************/ -#ifndef _FILESYSTEM_CTRU_H_ -#define _FILESYSTEM_CTRU_H_ +#ifndef _FILESYSTEM_CONSOLE_H_ +#define _FILESYSTEM_CONSOLE_H_ #if defined(PLUGIN) || defined(CODEC) #define FILEFUNCTIONS_DECLARED @@ -29,17 +29,17 @@ #endif /* PLUGIN || CODEC */ #ifndef OSFUNCTIONS_DECLARED -#define FS_PREFIX(_x_) ctru_ ## _x_ +#define FS_PREFIX(_x_) console_ ## _x_ void paths_init(void); #endif /* !OSFUNCTIONS_DECLARED */ -#endif /* _FILESYSTEM_CTRU_H_ */ +#endif /* _FILESYSTEM_CONSOLE_H_ */ #ifdef _FILE_H_ #include -#ifndef _FILESYSTEM_CTRU__FILE_H_ -#define _FILESYSTEM_CTRU__FILE_H_ +#ifndef _FILESYSTEM_CONSOLE__FILE_H_ +#define _FILESYSTEM_CONSOLE__FILE_H_ #ifdef RB_FILESYSTEM_OS #define FILEFUNCTIONS_DEFINED @@ -52,31 +52,31 @@ void paths_init(void); #include -int ctru_open(const char *name, int oflag, ...); -int ctru_creat(const char *name, mode_t mode); -int ctru_close(int fildes); -int ctru_ftruncate(int fildes, off_t length); -int ctru_fsync(int fildes); -off_t ctru_lseek(int fildes, off_t offset, int whence); -ssize_t ctru_read(int fildes, void *buf, size_t nbyte); -ssize_t ctru_write(int fildes, const void *buf, size_t nbyte); -int ctru_remove(const char *path); -int ctru_rename(const char *old, const char *new); -int ctru_modtime(const char *path, time_t modtime); -off_t ctru_filesize(int fildes); -int ctru_fsamefile(int fildes1, int fildes2); -int ctru_relate(const char *path1, const char *path2); -bool ctru_file_exists(const char *path); -ssize_t ctru_readlink(const char *path, char *buf, size_t bufsiz); +int console_open(const char *name, int oflag, ...); +int console_creat(const char *name, mode_t mode); +int console_close(int fildes); +int console_ftruncate(int fildes, off_t length); +int console_fsync(int fildes); +off_t console_lseek(int fildes, off_t offset, int whence); +ssize_t console_read(int fildes, void *buf, size_t nbyte); +ssize_t console_write(int fildes, const void *buf, size_t nbyte); +int console_remove(const char *path); +int console_rename(const char *old, const char *new); +int console_modtime(const char *path, time_t modtime); +off_t console_ffilesize(int fildes); +int console_fsamefile(int fildes1, int fildes2); +int console_relate(const char *path1, const char *path2); +bool console_file_exists(const char *path); +ssize_t console_readlink(const char *path, char *buf, size_t bufsiz); #endif /* !FILEFUNCTIONS_DECLARED */ -#endif /* _FILESYSTEM_CTRU__FILE_H_ */ +#endif /* _FILESYSTEM_CONSOLE__FILE_H_ */ #endif /* _FILE_H_ */ #ifdef _DIR_H_ -#ifndef _FILESYSTEM_CTRU__DIR_H_ -#define _FILESYSTEM_CTRU__DIR_H_ +#ifndef _FILESYSTEM_CONSOLE__DIR_H_ +#define _FILESYSTEM_CONSOLE__DIR_H_ #define DIRENT dirent struct dirent; @@ -99,17 +99,20 @@ typedef struct {} DIR; #define DIRFUNCTIONS_DEFINED #endif -DIR * ctru_opendir(const char *dirname); -struct dirent * ctru_readdir(DIR *dirp); -int ctru_readdir_r(DIR *dirp, struct dirent *entry, +DIR * console_opendir(const char *dirname); +struct dirent * console_readdir(DIR *dirp); +int console_readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result); -void ctru_rewinddir(DIR *dirp); -int ctru_closedir(DIR *dirp); -int ctru_mkdir(const char *path); -int ctru_rmdir(const char *path); -int ctru_samedir(DIR *dirp1, DIR *dirp2); -bool ctru_dir_exists(const char *dirname); +void console_rewinddir(DIR *dirp); +int console_closedir(DIR *dirp); +int console_mkdir(const char *path); +int console_rmdir(const char *path); +int console_samedir(DIR *dirp1, DIR *dirp2); +bool console_dir_exists(const char *dirname); + +struct dirinfo console_dir_get_info(DIR *dirp, struct dirent *entry); +const char* console_root_realpath(void); #endif /* !DIRFUNCTIONS_DECLARED */ -#endif /* _FILESYSTEM_CTRU__DIR_H_ */ +#endif /* _FILESYSTEM_CONSOLE__DIR_H_ */ #endif /* _DIR_H_ */ diff --git a/firmware/target/hosted/filesystem-hosted.h b/firmware/target/hosted/filesystem-hosted.h index 3716da76f7..38e0b73067 100644 --- a/firmware/target/hosted/filesystem-hosted.h +++ b/firmware/target/hosted/filesystem-hosted.h @@ -30,7 +30,7 @@ void * os_lc_open(const char *ospath); #ifndef OSFUNCTIONS_DECLARED int os_modtime(const char *path, time_t modtime); -off_t os_filesize(int osfd); +off_t os_ffilesize(int osfd); int os_fsamefile(int osfd1, int osfd2); int os_relate(const char *path1, const char *path2); bool os_file_exists(const char *ospath); diff --git a/firmware/target/hosted/filesystem-unix.c b/firmware/target/hosted/filesystem-unix.c index 5e4c2a7207..068c49cd6f 100644 --- a/firmware/target/hosted/filesystem-unix.c +++ b/firmware/target/hosted/filesystem-unix.c @@ -53,7 +53,7 @@ int os_modtime(const char *path, time_t modtime) return utime(path, ×); } -off_t os_filesize(int osfd) +off_t os_ffilesize(int osfd) { struct stat sb; diff --git a/firmware/target/hosted/filesystem-win32.h b/firmware/target/hosted/filesystem-win32.h index 68f77dbf98..2627d46b48 100644 --- a/firmware/target/hosted/filesystem-win32.h +++ b/firmware/target/hosted/filesystem-win32.h @@ -49,12 +49,12 @@ size_t strlcpy_utf16utf8(char *buffer, const unsigned short *utf16, #ifndef OSFUNCTIONS_DECLARED /* Wrap for off_t <=> long conversions */ -static inline off_t os_filesize_(int osfd) +static inline off_t os_ffilesize_(int osfd) { return _filelength(osfd); } static inline int os_ftruncate_(int osfd, off_t length) { return _chsize(osfd, length); } -#define os_filesize os_filesize_ +#define os_ffilesize os_ffilesize_ #define os_ftruncate os_ftruncate_ #define os_fsync _commit #define os_fstat _fstat diff --git a/firmware/target/hosted/ctru/backlight-ctru.c b/firmware/target/hosted/game_console/3ds/backlight-3ds.c similarity index 99% rename from firmware/target/hosted/ctru/backlight-ctru.c rename to firmware/target/hosted/game_console/3ds/backlight-3ds.c index f72ea46d1e..c171e5af56 100644 --- a/firmware/target/hosted/ctru/backlight-ctru.c +++ b/firmware/target/hosted/game_console/3ds/backlight-3ds.c @@ -34,7 +34,7 @@ #include "lcd.h" #include "debug.h" -#include "luminance-ctru.h" +#include "luminance-3ds.h" /* TODO: To use calibrated values in rockbox, MIN_BRIGHTNESS_SETTING (etc) would need to be diff --git a/firmware/target/hosted/ctru/button-ctru.c b/firmware/target/hosted/game_console/3ds/button-3ds.c similarity index 82% rename from firmware/target/hosted/ctru/button-ctru.c rename to firmware/target/hosted/game_console/3ds/button-3ds.c index a03c0d0eb5..a7144b99e5 100644 --- a/firmware/target/hosted/ctru/button-ctru.c +++ b/firmware/target/hosted/game_console/3ds/button-3ds.c @@ -33,7 +33,7 @@ #include "kernel.h" #include "backlight.h" #include "system.h" -#include "button-ctru.h" +#include "button-console.h" #include "buttonmap.h" #include "debug.h" #include "powermgmt.h" @@ -53,6 +53,8 @@ static enum { STATE_DOWN = 1, } last_touch_state = STATE_UNKNOWN; +static bool touch_enabled = true; + static double map_values(double n, double source_start, double source_end, double dest_start, double dest_end, int decimal_precision ) { double delta_start = source_end - source_start; double delta_end = dest_end - dest_start; @@ -86,6 +88,8 @@ void update_sound_slider_level(void) } } +#define CPAD_DEADZONE 64 + int button_read_device(int* data) { int key = BUTTON_NONE; @@ -97,14 +101,26 @@ int button_read_device(int* data) hidScanInput(); u32 kDown = hidKeysDown(); - + if (kDown & KEY_SELECT) { touchscreen_set_mode(touchscreen_get_mode() == TOUCHSCREEN_POINT ? TOUCHSCREEN_BUTTON : TOUCHSCREEN_POINT); printf("Touchscreen mode: %s\n", touchscreen_get_mode() == TOUCHSCREEN_POINT ? "TOUCHSCREEN_POINT" : "TOUCHSCREEN_BUTTON"); } - + u32 kHeld = hidKeysHeld(); + /* Circle pad → directional buttons (alternative to D-pad) */ + circlePosition cpad; + hidCircleRead(&cpad); + if (cpad.dx > CPAD_DEADZONE) + kHeld |= KEY_DRIGHT; + else if (cpad.dx < -CPAD_DEADZONE) + kHeld |= KEY_DLEFT; + if (cpad.dy > CPAD_DEADZONE) + kHeld |= KEY_DUP; + else if (cpad.dy < -CPAD_DEADZONE) + kHeld |= KEY_DDOWN; + /* rockbox will handle button repeats */ kDown |= kHeld; @@ -138,22 +154,25 @@ int button_read_device(int* data) } touchPosition touch; - hidTouchRead(&touch); + if (touch_enabled) + { + hidTouchRead(&touch); - /* Generate UP and DOWN events */ - if (kDown & KEY_TOUCH) { - last_touch_state = STATE_DOWN; - } - else { - last_touch_state = STATE_UP; - } + /* Generate UP and DOWN events */ + if (kDown & KEY_TOUCH) { + last_touch_state = STATE_DOWN; + } + else { + last_touch_state = STATE_UP; + } - last_x = touch.px; - last_y = touch.py; + last_x = touch.px; + last_y = touch.py; - int tkey = touchscreen_to_pixels(last_x, last_y, data); - if (last_touch_state == STATE_DOWN) { - key |= tkey; + int tkey = touchscreen_to_pixels(last_x, last_y, data); + if (last_touch_state == STATE_DOWN) { + key |= tkey; + } } update_sound_slider_level(); @@ -169,7 +188,7 @@ void button_init_device(void) #ifndef HAS_BUTTON_HOLD void touchscreen_enable_device(bool en) { - (void)en; + touch_enabled = en; } #endif diff --git a/firmware/target/hosted/ctru/filesystem-ctru.c b/firmware/target/hosted/game_console/3ds/filesystem-3ds.c similarity index 76% rename from firmware/target/hosted/ctru/filesystem-ctru.c rename to firmware/target/hosted/game_console/3ds/filesystem-3ds.c index 07a3b012de..d7b3688a4e 100644 --- a/firmware/target/hosted/ctru/filesystem-ctru.c +++ b/firmware/target/hosted/game_console/3ds/filesystem-3ds.c @@ -37,21 +37,13 @@ void paths_init(void) { - /* is this needed in 3DS? */ -#if 0 - char config_dir[MAX_PATH]; - - const char *home = "/3ds"; - mkdir("/3ds/.rockbox" __MKDIR_MODE_ARG); - - snprintf(config_dir, sizeof(config_dir), "%s/.config", home); - mkdir(config_dir __MKDIR_MODE_ARG); - snprintf(config_dir, sizeof(config_dir), "%s/.config/rockbox.org", home); - mkdir(config_dir __MKDIR_MODE_ARG); - /* Plugin data directory */ - snprintf(config_dir, sizeof(config_dir), "%s/.config/rockbox.org/rocks.data", home); - mkdir(config_dir __MKDIR_MODE_ARG); -#endif + mkdir(ROCKBOX_DIR __MKDIR_MODE_ARG); + mkdir(ROCKBOX_DIR "/backdrops" __MKDIR_MODE_ARG); + mkdir(ROCKBOX_DIR "/eqs" __MKDIR_MODE_ARG); + mkdir(ROCKBOX_DIR "/icons" __MKDIR_MODE_ARG); + mkdir(ROCKBOX_DIR "/rocks.data" __MKDIR_MODE_ARG); + mkdir(ROCKBOX_DIR "/themes" __MKDIR_MODE_ARG); + mkdir(ROCKBOX_DIR "/wps" __MKDIR_MODE_ARG); } /* only sdcard volume is accesible to the user */ diff --git a/firmware/target/hosted/ctru/lc-ctru.c b/firmware/target/hosted/game_console/3ds/lc-3ds.c similarity index 90% rename from firmware/target/hosted/ctru/lc-ctru.c rename to firmware/target/hosted/game_console/3ds/lc-3ds.c index af7cfc8655..110e992804 100644 --- a/firmware/target/hosted/ctru/lc-ctru.c +++ b/firmware/target/hosted/game_console/3ds/lc-3ds.c @@ -29,20 +29,19 @@ #include "system.h" #include "load_code.h" -#include "filesystem-ctru.h" +#include "filesystem-console.h" #include "debug.h" -extern u32 __ctrl_code_allocator_pages; +/* reserve only the required memory for rockbox app, + the rest will be used to load .so objects */ +u32 __ctru_heap_size = MEMORYSIZE * 1024 * 1024; +u32 __ctru_linear_heap_size = MEMORYSIZE * 1024 * 1024; void* programResolver(const char* sym, void *userData); void * lc_open(const char *filename, unsigned char *buf, size_t buf_size) { DEBUGF("dlopen(path=\"%s\")\n", filename); - /* We need to increase __ctrl_code_allocator_pages value to 8 MB - to enable big plugins */ - __ctrl_code_allocator_pages = 2048; - /* note: the 3ds dlopen implementation needs a custom resolver for the unresolved symbols in shared objects */ /* void *handle = dlopen(filename, RTLD_NOW | RTLD_LOCAL); */ diff --git a/firmware/target/hosted/ctru/lc-program-resolver.c b/firmware/target/hosted/game_console/3ds/lc-program-resolver.c similarity index 100% rename from firmware/target/hosted/ctru/lc-program-resolver.c rename to firmware/target/hosted/game_console/3ds/lc-program-resolver.c diff --git a/firmware/target/hosted/ctru/lcd-bitmap.c b/firmware/target/hosted/game_console/3ds/lcd-bitmap-3ds.c similarity index 99% rename from firmware/target/hosted/ctru/lcd-bitmap.c rename to firmware/target/hosted/game_console/3ds/lcd-bitmap-3ds.c index 20e344e349..1d5b6bf1d5 100644 --- a/firmware/target/hosted/ctru/lcd-bitmap.c +++ b/firmware/target/hosted/game_console/3ds/lcd-bitmap-3ds.c @@ -33,7 +33,7 @@ #include "debug.h" #include "system.h" -#include "button-ctru.h" +#include "button-console.h" #include "screendump.h" #include "lcd-target.h" diff --git a/firmware/target/hosted/ctru/luminance-ctru.c b/firmware/target/hosted/game_console/3ds/luminance-3ds.c similarity index 99% rename from firmware/target/hosted/ctru/luminance-ctru.c rename to firmware/target/hosted/game_console/3ds/luminance-3ds.c index 7d1d0a3310..6e59686a2a 100644 --- a/firmware/target/hosted/ctru/luminance-ctru.c +++ b/firmware/target/hosted/game_console/3ds/luminance-3ds.c @@ -28,7 +28,7 @@ #include <3ds.h> #include -#include "luminance-ctru.h" +#include "luminance-3ds.h" // For accessing physmem uncached (and directly) #define PA_PTR(addr) (void *)((u32)(addr) | 1 << 31) diff --git a/firmware/target/hosted/ctru/luminance-ctru.h b/firmware/target/hosted/game_console/3ds/luminance-3ds.h similarity index 93% rename from firmware/target/hosted/ctru/luminance-ctru.h rename to firmware/target/hosted/game_console/3ds/luminance-3ds.h index 148935b790..c28117aa3e 100644 --- a/firmware/target/hosted/ctru/luminance-ctru.h +++ b/firmware/target/hosted/game_console/3ds/luminance-3ds.h @@ -24,8 +24,8 @@ * reasonable ways as different from the original version. */ -#ifndef _LUMINANCE_CTRU_H_ -#define _LUMINANCE_CTRU_H_ +#ifndef _LUMINANCE_CONSOLE_H_ +#define _LUMINANCE_CONSOLE_H_ #include <3ds/types.h> @@ -37,4 +37,4 @@ u32 getMinLuminancePreset(void); u32 getMaxLuminancePreset(void); u32 getCurrentLuminance(bool top); -#endif /* _LUMINANCE_CTRU_H_ */ +#endif /* _LUMINANCE_CONSOLE_H_ */ diff --git a/firmware/target/hosted/ctru/pcm-ctru.c b/firmware/target/hosted/game_console/3ds/pcm-3ds.c similarity index 97% rename from firmware/target/hosted/ctru/pcm-ctru.c rename to firmware/target/hosted/game_console/3ds/pcm-3ds.c index 4c296b6284..3279429223 100644 --- a/firmware/target/hosted/ctru/pcm-ctru.c +++ b/firmware/target/hosted/game_console/3ds/pcm-3ds.c @@ -33,6 +33,9 @@ #include <3ds/synchronization.h> #include <3ds/allocator/linear.h> +#include "sys_atomic.h" +#include "sys_thread.h" + #include "config.h" #include "debug.h" #include "sound.h" @@ -56,12 +59,6 @@ /*#define LOGF_ENABLE*/ #include "logf.h" -#ifdef DEBUG -extern bool debug_audio; -#endif - -extern const char *audiodev; - /* Bytes left in the Rockbox PCM frame buffer. */ static size_t _pcm_buffer_size = 0; @@ -207,7 +204,7 @@ static void sink_set_freq_nolock(uint16_t freq) mix[1] = 1.0; ndspChnSetMix(0, mix); - memset(_dsp_wave_bufs, 0, sizeof(_dsp_wave_bufs) * ARRAY_SIZE(_dsp_wave_bufs)); + memset(_dsp_wave_bufs, 0, sizeof(ndspWaveBuf) * ARRAY_SIZE(_dsp_wave_bufs)); const size_t wave_buffer_size = MIX_FRAME_SAMPLES * 2 * sizeof(s16); size_t buffer_size = wave_buffer_size * ARRAY_SIZE(_dsp_wave_bufs); @@ -233,7 +230,7 @@ static void sink_set_freq_nolock(uint16_t freq) NULL, 32 * 1024, /* 32kB stack size */ 0x18, /* high priority */ - -1, /* run on any core */ false); + -1, /* run on any core */ false); } static void sink_dma_start(const void *addr, size_t size) @@ -364,6 +361,7 @@ struct pcm_sink builtin_pcm_sink = { .samprs = hw_freq_sampr, .num_samprs = HW_NUM_FREQ, .default_freq = HW_FREQ_DEFAULT, + .volume_type = PCM_NATIVE_VOLUME_TYPE, }, .ops = { .init = sink_dma_init, diff --git a/firmware/target/hosted/ctru/powermgmt-ctru.c b/firmware/target/hosted/game_console/3ds/powermgmt-3ds.c similarity index 100% rename from firmware/target/hosted/ctru/powermgmt-ctru.c rename to firmware/target/hosted/game_console/3ds/powermgmt-3ds.c diff --git a/firmware/target/hosted/ctru/system-ctru.c b/firmware/target/hosted/game_console/3ds/system-3ds.c similarity index 88% rename from firmware/target/hosted/ctru/system-ctru.c rename to firmware/target/hosted/game_console/3ds/system-3ds.c index d1f760ad32..b6dc917dce 100644 --- a/firmware/target/hosted/ctru/system-ctru.c +++ b/firmware/target/hosted/game_console/3ds/system-3ds.c @@ -25,6 +25,7 @@ /* this includes a couple of 3ds headers */ #include "bfile.h" +#include "sys_timer.h" #include <3ds/types.h> #include <3ds/allocator/linear.h> @@ -32,9 +33,9 @@ #include "system.h" #include "kernel.h" -#include "thread-ctru.h" -#include "system-ctru.h" -#include "button-ctru.h" +#include "thread-console.h" +#include "system-console.h" +#include "button-console.h" #include "lcd-bitmap.h" #include "panic.h" #include "debug.h" @@ -55,28 +56,26 @@ FS_Archive sdmcArchive; void ctru_sys_quit(void) { - sys_poweroff(); + sim_do_exit(); } void power_off(void) { - /* since sim_thread_shutdown() grabs the mutex we need to let it free, - * otherwise sys_wait_thread will deadlock */ + /* shut down Rockbox kernel threads */ struct thread_entry* t = sim_thread_unlock(); sim_thread_shutdown(); /* lock again before entering the scheduler */ sim_thread_lock(t); - /* sim_thread_shutdown() will cause sim_do_exit() to be called via longjmp, - * but only if we let the sdl thread scheduler exit the other threads */ - while(1) yield(); + sim_do_exit(); } void sim_do_exit() { sim_kernel_shutdown(); sys_timer_quit(); - /* TODO: quit_everything() */ + mcuhwc_close(); + cfguExit(); exit(EXIT_SUCCESS); } diff --git a/firmware/target/hosted/ctru/app/adc-target.h b/firmware/target/hosted/game_console/adc-target.h similarity index 100% rename from firmware/target/hosted/ctru/app/adc-target.h rename to firmware/target/hosted/game_console/adc-target.h diff --git a/firmware/target/hosted/ctru/app/backlight-target.h b/firmware/target/hosted/game_console/backlight-target.h similarity index 100% rename from firmware/target/hosted/ctru/app/backlight-target.h rename to firmware/target/hosted/game_console/backlight-target.h diff --git a/firmware/target/hosted/ctru/button-ctru.h b/firmware/target/hosted/game_console/button-console.h similarity index 92% rename from firmware/target/hosted/ctru/button-ctru.h rename to firmware/target/hosted/game_console/button-console.h index 606c7d09f4..2cfea0cfea 100644 --- a/firmware/target/hosted/ctru/button-ctru.h +++ b/firmware/target/hosted/game_console/button-console.h @@ -19,8 +19,8 @@ ****************************************************************************/ -#ifndef __BUTTON_CTRU_H__ -#define __BUTTON_CTRU_H__ +#ifndef __BUTTON_CONSOLE_H__ +#define __BUTTON_CONSOLE_H__ #include #include "config.h" @@ -30,4 +30,4 @@ bool button_hold(void); void button_init_device(void); int button_read_device(int *data); -#endif /* __BUTTON_CTRU_H__ */ +#endif /* __BUTTON_CONSOLE_H__ */ diff --git a/firmware/target/hosted/ctru/app/button-target.h b/firmware/target/hosted/game_console/button-target.h similarity index 100% rename from firmware/target/hosted/ctru/app/button-target.h rename to firmware/target/hosted/game_console/button-target.h diff --git a/firmware/target/hosted/ctru/buttonmap.h b/firmware/target/hosted/game_console/buttonmap.h similarity index 100% rename from firmware/target/hosted/ctru/buttonmap.h rename to firmware/target/hosted/game_console/buttonmap.h diff --git a/firmware/target/hosted/ctru/kernel-ctru.c b/firmware/target/hosted/game_console/kernel-console.c similarity index 87% rename from firmware/target/hosted/ctru/kernel-ctru.c rename to firmware/target/hosted/game_console/kernel-console.c index 27d7f2cee0..f68b807a71 100644 --- a/firmware/target/hosted/ctru/kernel-ctru.c +++ b/firmware/target/hosted/game_console/kernel-console.c @@ -5,8 +5,9 @@ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ + * $Id$ * - * Copyright (C) 2025 Mauricio G. + * Copyright (C) 2002 by Felix Arends * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -21,13 +22,16 @@ #include #include #include -#include "system-ctru.h" -#include "thread-ctru.h" +#include "system-console.h" +#include "thread-console.h" #include "kernel.h" #include "thread.h" #include "panic.h" #include "debug.h" +#include "sys_thread.h" +#include "sys_timer.h" + static int tick_timer_id; long start_tick; @@ -35,7 +39,7 @@ long start_tick; static sysCond *sim_thread_cond; /* Mutex to serialize changing levels and exclude other threads while * inside a handler */ -static RecursiveLock sim_irq_mtx; +static sysMutex *sim_irq_mtx; /* Level: 0 = enabled, not 0 = disabled */ static int volatile interrupt_level = HIGHEST_IRQ_LEVEL; /* How many handers waiting? Not strictly needed because CondSignal is a @@ -54,7 +58,7 @@ static int status_reg = 0; */ int set_irq_level(int level) { - RecursiveLock_Lock(&sim_irq_mtx); + sys_mutex_lock(sim_irq_mtx); int oldlevel = interrupt_level; @@ -68,13 +72,13 @@ int set_irq_level(int level) interrupt_level = level; /* save new level */ - RecursiveLock_Unlock(&sim_irq_mtx); + sys_mutex_unlock(sim_irq_mtx); return oldlevel; } void sim_enter_irq_handler(void) { - RecursiveLock_Lock(&sim_irq_mtx); + sys_mutex_lock(sim_irq_mtx); handlers_pending++; /* Check each time before proceeding: disabled->enabled->...->disabled @@ -82,7 +86,7 @@ void sim_enter_irq_handler(void) * the mutex; a handler can also leave "interrupts" disabled during * its execution */ while (interrupt_level != 0) - sys_cond_wait(sim_thread_cond, &sim_irq_mtx); + sys_cond_wait(sim_thread_cond, sim_irq_mtx); status_reg = 1; } @@ -94,12 +98,18 @@ void sim_exit_irq_handler(void) sys_cond_signal(sim_thread_cond); status_reg = 0; - RecursiveLock_Unlock(&sim_irq_mtx); + sys_mutex_unlock(sim_irq_mtx); } static bool sim_kernel_init(void) { - RecursiveLock_Init(&sim_irq_mtx); + sim_irq_mtx = (sysMutex *) sys_mutex_create(); + if (sim_irq_mtx == NULL) + { + panicf("Cannot create sim_irq_mtx\n"); + return false; + } + sim_thread_cond = sys_cond_create(); if (sim_thread_cond == NULL) { @@ -116,7 +126,8 @@ void sim_kernel_shutdown(void) while(handlers_pending > 0) sys_delay(10); - sys_cond_destroy(sim_thread_cond); + free(sim_irq_mtx); + free(sim_thread_cond); } u32 tick_timer(u32 interval, void *param) @@ -161,5 +172,4 @@ void tick_start(unsigned int interval_in_ms) } tick_timer_id = sys_add_timer(interval_in_ms, tick_timer, NULL); -} - +} \ No newline at end of file diff --git a/firmware/target/hosted/ctru/lcd-bitmap.h b/firmware/target/hosted/game_console/lcd-bitmap.h similarity index 100% rename from firmware/target/hosted/ctru/lcd-bitmap.h rename to firmware/target/hosted/game_console/lcd-bitmap.h diff --git a/firmware/target/hosted/ctru/lcd-target.h b/firmware/target/hosted/game_console/lcd-target.h similarity index 100% rename from firmware/target/hosted/ctru/lcd-target.h rename to firmware/target/hosted/game_console/lcd-target.h diff --git a/firmware/target/hosted/ctru/lib/bfile/bfile.c b/firmware/target/hosted/game_console/lib/bfile/bfile-3ds.c similarity index 84% rename from firmware/target/hosted/ctru/lib/bfile/bfile.c rename to firmware/target/hosted/game_console/lib/bfile/bfile-3ds.c index e7a8625e71..2aa5f53f64 100644 --- a/firmware/target/hosted/ctru/lib/bfile/bfile.c +++ b/firmware/target/hosted/game_console/lib/bfile/bfile-3ds.c @@ -12,59 +12,6 @@ #endif #include "logf.h" -// rw mutex implementation -void sync_RWMutexInit(sync_RWMutex *m) { - LightLock_Init(&m->shared); - CondVar_Init(&m->reader_q); - CondVar_Init(&m->writer_q); - m->active_readers = 0; - m->active_writers = 0; - m->waiting_writers = 0; -} - -static inline LightLock *unique_lock(LightLock *lk) { - LightLock_Lock(lk); - return lk; -} - -void sync_RWMutexRLock(sync_RWMutex *m) { - LightLock *lk = unique_lock(&m->shared); - while (m->waiting_writers != 0) { - CondVar_Wait(&m->reader_q, lk); - } - ++m->active_readers; - LightLock_Unlock(lk); -} - -void sync_RWMutexRUnlock(sync_RWMutex *m) { - LightLock *lk = unique_lock(&m->shared); - --m->active_readers; - LightLock_Unlock(lk); - CondVar_Signal(&m->writer_q); -} - -void sync_RWMutexLock(sync_RWMutex *m) { - LightLock *lk = unique_lock(&m->shared); - ++m->waiting_writers; - while ((m->active_readers != 0) || (m->active_writers != 0)) { - CondVar_Wait(&m->writer_q, lk); - } - ++m->active_writers; - LightLock_Unlock(lk); -} - -void sync_RWMutexUnlock(sync_RWMutex *m) { - LightLock *lk = unique_lock(&m->shared); - --m->waiting_writers; - --m->active_writers; - if (m->waiting_writers > 0) { - CondVar_Signal(&m->writer_q); - } else { - CondVar_Broadcast(&m->reader_q); - } - LightLock_Unlock(lk); -} - // page reader implementation file_error_t readPage(PageReader *p, u64 offset) { diff --git a/firmware/target/hosted/ctru/lib/bfile/bfile-internal.h b/firmware/target/hosted/game_console/lib/bfile/bfile-internal.h similarity index 74% rename from firmware/target/hosted/ctru/lib/bfile/bfile-internal.h rename to firmware/target/hosted/game_console/lib/bfile/bfile-internal.h index 62977a68ad..745dfc4a87 100644 --- a/firmware/target/hosted/ctru/lib/bfile/bfile-internal.h +++ b/firmware/target/hosted/game_console/lib/bfile/bfile-internal.h @@ -9,17 +9,7 @@ #include #include -#include <3ds/gfx.h> -#ifdef RGB565 -#undef RGB565 -#endif - -#include <3ds/svc.h> -#include <3ds/types.h> -#include <3ds/thread.h> -#include <3ds/result.h> -#include <3ds/services/fs.h> -#include <3ds/synchronization.h> +#include "sys_thread.h" /* #define MALLOC_DEBUG #include "rmalloc/rmalloc.h" */ @@ -32,29 +22,29 @@ typedef struct { \ } type_name##_t; /* single mutex implementation */ -#define sync_Mutex LightLock +#define sync_Mutex sysMutex static inline void sync_MutexInit(sync_Mutex* m) { - LightLock_Init(m); + sys_mutex_init(m); } /* mutex unlock */ static inline void sync_MutexLock(sync_Mutex* m) { - LightLock_Lock(m); + sys_mutex_lock(m); } /* mutex lock */ static inline void sync_MutexUnlock(sync_Mutex* m) { - LightLock_Unlock(m); + sys_mutex_unlock(m); } /* read_write mutex implementation */ typedef struct { - sync_Mutex shared; - CondVar reader_q; - CondVar writer_q; - int active_readers; - int active_writers; - int waiting_writers; + sync_Mutex shared; + sysCond reader_q; + sysCond writer_q; + int active_readers; + int active_writers; + int waiting_writers; } sync_RWMutex; void sync_RWMutexInit(sync_RWMutex *m); @@ -68,6 +58,7 @@ two_type_value(int, const char*, n, err, int_error); two_type_value(struct stat, const char*, fi, err, stat_error); typedef const char* file_error_t; +#ifdef N3DS typedef struct { Handle file; s64 start; @@ -77,5 +68,6 @@ typedef struct { u8 *buffer; } PageReader; +#endif #endif /* _BFILE_INTERNAL_H_ */ diff --git a/firmware/target/hosted/game_console/lib/bfile/bfile-rwmutex.c b/firmware/target/hosted/game_console/lib/bfile/bfile-rwmutex.c new file mode 100644 index 0000000000..d7ff6a2807 --- /dev/null +++ b/firmware/target/hosted/game_console/lib/bfile/bfile-rwmutex.c @@ -0,0 +1,61 @@ + +#include "bfile.h" + +/* Define LOGF_ENABLE to enable logf output in this file */ +#if 0 +#define LOGF_ENABLE +#endif +#include "logf.h" + +/* rw mutex implementation */ +void sync_RWMutexInit(sync_RWMutex *m) { + sys_mutex_init(&m->shared); + sys_cond_init(&m->reader_q); + sys_cond_init(&m->writer_q); + m->active_readers = 0; + m->active_writers = 0; + m->waiting_writers = 0; +} + +static inline sysMutex *unique_lock(sysMutex *lk) { + sys_mutex_lock(lk); + return lk; +} + +void sync_RWMutexRLock(sync_RWMutex *m) { + sysMutex *lk = unique_lock(&m->shared); + while (m->waiting_writers != 0) { + sys_cond_wait(&m->reader_q, lk); + } + ++m->active_readers; + sys_mutex_unlock(lk); +} + +void sync_RWMutexRUnlock(sync_RWMutex *m) { + sysMutex *lk = unique_lock(&m->shared); + --m->active_readers; + sys_mutex_unlock(lk); + sys_cond_signal(&m->writer_q); +} + +void sync_RWMutexLock(sync_RWMutex *m) { + sysMutex *lk = unique_lock(&m->shared); + ++m->waiting_writers; + while ((m->active_readers != 0) || (m->active_writers != 0)) { + sys_cond_wait(&m->writer_q, lk); + } + ++m->active_writers; + sys_mutex_unlock(lk); +} + +void sync_RWMutexUnlock(sync_RWMutex *m) { + sysMutex *lk = unique_lock(&m->shared); + --m->waiting_writers; + --m->active_writers; + if (m->waiting_writers > 0) { + sys_cond_signal(&m->writer_q); + } else { + sys_cond_broadcast(&m->reader_q); + } + sys_mutex_unlock(lk); +} \ No newline at end of file diff --git a/firmware/target/hosted/ctru/lib/bfile/bfile.h b/firmware/target/hosted/game_console/lib/bfile/bfile.h similarity index 91% rename from firmware/target/hosted/ctru/lib/bfile/bfile.h rename to firmware/target/hosted/game_console/lib/bfile/bfile.h index bdaeb3810e..f6ec1f31fd 100644 --- a/firmware/target/hosted/ctru/lib/bfile/bfile.h +++ b/firmware/target/hosted/game_console/lib/bfile/bfile.h @@ -6,7 +6,7 @@ static const int defaultPageSize = 0x20000; // default page buffer size, 128 kB // NewPageReader creates a new PageReader struct with pageSize. -PageReader *NewPageReader(Handle file, s64 pageSize); +PageReader *NewPageReader(sys_handle_type file, s64 pageSize); // Free all memory associated to the PageReader pointer. void PageReader_Free(PageReader *p); @@ -17,10 +17,12 @@ int_error_t PageReader_ReadAt(PageReader *p, u8 *buffer, size_t size, off_t offs // WriteAt writes size bytes from the PageReader starting at offset. int_error_t PageReader_WriteAt(PageReader *p, u8 *buffer, size_t size, off_t offset, bool flush_cache); +#ifdef N3DS // NewPageReader creates a new PageReader struct with pageSize, for directory use. PageReader *NewPageReaderDirectory(Handle file, s64 pageSize); // ReadAt reads one directory entry from memory. int_error_t PageReader_ReadDir(PageReader *p, FS_DirectoryEntry *entry); +#endif #endif /* _B_FILE_H_ */ diff --git a/firmware/target/hosted/game_console/lib/sys_atomic.c b/firmware/target/hosted/game_console/lib/sys_atomic.c new file mode 100644 index 0000000000..22ad664ba1 --- /dev/null +++ b/firmware/target/hosted/game_console/lib/sys_atomic.c @@ -0,0 +1,60 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2026 Mauricio G. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include "debug.h" +#include "logf.h" + +#include "sys_atomic.h" + +bool _AtomicCAS(u32 *ptr, int oldval, int newval) +{ + return (bool) __sync_bool_compare_and_swap(ptr, oldval, newval); +} + +bool _AtomicTryLock(int *lock) +{ + return __sync_lock_test_and_set(lock, 1) == 0; +} + +void sys_delay(u32 ms); +#define CPUPauseInstruction() +void AtomicLock(int *lock) +{ + int iterations = 0; + while (!_AtomicTryLock(lock)) { + if (iterations < 32) { + iterations++; + CPUPauseInstruction(); + } else { + sys_delay(0); + } + } +} + +void AtomicUnlock(int *lock) +{ + __sync_lock_release(lock); +} diff --git a/firmware/target/hosted/game_console/lib/sys_atomic.h b/firmware/target/hosted/game_console/lib/sys_atomic.h new file mode 100644 index 0000000000..722eff2a77 --- /dev/null +++ b/firmware/target/hosted/game_console/lib/sys_atomic.h @@ -0,0 +1,37 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2026 Mauricio G. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#ifndef __SYSATOMIC_H__ +#define __SYSATOMIC_H__ + +#include "sys_types.h" + +#define AtomicAdd(ptr, value) __sync_fetch_and_add((u32 *)(ptr), value) + +bool _AtomicCAS(u32 *ptr, int oldval, int newval); +#define AtomicGet(ptr) __atomic_load_n((u32*)(ptr), __ATOMIC_SEQ_CST) +#define AtomicSet(ptr, value) __sync_lock_test_and_set((u32 *)(ptr), value) +#define AtomicCAS(ptr, oldvalue, newvalue) _AtomicCAS((u32 *)(ptr), oldvalue, newvalue) +void AtomicLock(int *lock); +void AtomicUnlock(int *lock); + +#endif /* #ifndef __SYSATOMIC_H__ */ + diff --git a/firmware/target/hosted/game_console/lib/sys_cond_generic.c b/firmware/target/hosted/game_console/lib/sys_cond_generic.c new file mode 100644 index 0000000000..15b58c1505 --- /dev/null +++ b/firmware/target/hosted/game_console/lib/sys_cond_generic.c @@ -0,0 +1,154 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2025 Mauricio Ga. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include "sys_thread.h" +#include "sys_timer.h" +#include "debug.h" +#include "logf.h" + +/* sysCond */ +void sys_cond_init(sysCond *cond) +{ + if (cond) { + memset(cond, 0, sizeof(sysCond)); + sys_mutex_init(&cond->lock); + sys_sem_init(&cond->wait_sem, 0); + sys_sem_init(&cond->wait_done, 0); + cond->waiting = cond->signals = 0; + } +} + +sysCond *sys_cond_create(void) +{ + sysCond *cond; + + cond = (sysCond *) malloc(sizeof(sysCond)); + if (cond) { + sys_cond_init(cond); + } else { + DEBUGF("sys_cond_create: out of memory.\n"); + } + return cond; +} + +/* Destroy a condition variable */ +void sys_cond_destroy(sysCond *cond) +{ + if (cond) { + free(cond); + } +} + +/* Restart one of the threads that are waiting on the condition variable */ +int sys_cond_signal(sysCond *cond) +{ + if (!cond) { + DEBUGF("sys_cond_signal: Invalid param 'cond'\n"); + return -1; + } + + /* If there are waiting threads not already signalled, then + signal the condition and wait for the thread to respond. + */ + sys_mutex_lock(&cond->lock); + if (cond->waiting > cond->signals) { + ++cond->signals; + sys_sem_post(&cond->wait_sem); + sys_mutex_unlock(&cond->lock); + sys_sem_wait(&cond->wait_done); + } else { + sys_mutex_unlock(&cond->lock); + } + + return 0; +} + +/* Restart all threads that are waiting on the condition variable */ +int sys_cond_broadcast(sysCond *cond) +{ + if (!cond) { + DEBUGF("sys_cond_signal: Invalid param 'cond'\n"); + return -1; + } + + /* If there are waiting threads not already signalled, then + signal the condition and wait for the thread to respond. + */ + sys_mutex_lock(&cond->lock); + if (cond->waiting > cond->signals) { + int i, num_waiting; + + num_waiting = (cond->waiting - cond->signals); + cond->signals = cond->waiting; + for (i = 0; i < num_waiting; ++i) { + sys_sem_post(&cond->wait_sem); + } + /* Now all released threads are blocked here, waiting for us. + Collect them all (and win fabulous prizes!) :-) + */ + sys_mutex_unlock(&cond->lock); + for (i = 0; i < num_waiting; ++i) { + sys_sem_wait(&cond->wait_done); + } + } else { + sys_mutex_unlock(&cond->lock); + } + + return 0; +} + +int sys_cond_wait(sysCond *cond, sysMutex *mutex) +{ + if (!cond) { + DEBUGF("sys_cond_signal: Invalid param 'cond'\n"); + return -1; + } + + sys_mutex_lock(&cond->lock); + ++cond->waiting; + sys_mutex_unlock(&cond->lock); + + /* Unlock the mutex, as is required by condition variable semantics */ + sys_mutex_unlock(mutex); + + /* Wait for a signal */ + int retval = sys_sem_wait(&cond->wait_sem); + + sys_mutex_lock(&cond->lock); + if (cond->signals > 0) { + /* We always notify the signal thread that we are done */ + sys_sem_post(&cond->wait_done); + + /* Signal handshake complete */ + --cond->signals; + } + --cond->waiting; + sys_mutex_unlock(&cond->lock); + + /* Lock the mutex, as is required by condition variable semantics */ + sys_mutex_lock(mutex); + + return retval; +} diff --git a/firmware/target/hosted/ctru/lib/sys_dir.c b/firmware/target/hosted/game_console/lib/sys_dir_3ds.c similarity index 91% rename from firmware/target/hosted/ctru/lib/sys_dir.c rename to firmware/target/hosted/game_console/lib/sys_dir_3ds.c index 1088110b94..b83d727b4b 100644 --- a/firmware/target/hosted/ctru/lib/sys_dir.c +++ b/firmware/target/hosted/game_console/lib/sys_dir_3ds.c @@ -26,6 +26,7 @@ #include <3ds/util/utf.h> /* this includes a couple of 3ds headers */ #include "sys_file.h" +#include "sys_file_internal.h" #include "config.h" #include "debug.h" @@ -46,7 +47,7 @@ static struct dirstr_desc { struct filestr_base stream; /* basic stream info (first!) */ struct dirent entry; /* current parsed entry information */ -} open_streams[MAX_OPEN_DIRS] = +} open_dir_streams[MAX_OPEN_DIRS] = { [0 ... MAX_OPEN_FILES-1] = { .stream = { .cache = NULL } } }; @@ -58,7 +59,7 @@ static struct dirstr_desc * get_dirstr(DIR *dirp) { struct dirstr_desc *dir = (struct dirstr_desc *)dirp; - if (!PTR_IN_ARRAY(open_streams, dir, MAX_OPEN_DIRS)) + if (!PTR_IN_ARRAY(open_dir_streams, dir, MAX_OPEN_DIRS)) dir = NULL; else if (dir->stream.cache != NULL) return dir; @@ -71,7 +72,7 @@ static struct dirstr_desc * get_dirstr(DIR *dirp) } else { - logf("dir #%d: dir not open\n", (int)(dir - open_streams)); + logf("dir #%d: dir not open\n", (int)(dir - open_dir_streams)); errnum = EBADF; } @@ -104,7 +105,7 @@ static struct dirstr_desc * alloc_dirstr(void) { for (unsigned int dd = 0; dd < MAX_OPEN_DIRS; dd++) { - struct dirstr_desc *dir = &open_streams[dd]; + struct dirstr_desc *dir = &open_dir_streams[dd]; if (dir->stream.cache == NULL) return dir; } @@ -119,19 +120,10 @@ u32 fs_error(void) { return err; } -/* Initialize the base descriptor */ -static void filestr_base_init(struct filestr_base *stream) -{ - stream->cache = NULL; - stream->handle = 0; - stream->size = 0; - LightLock_Init(&stream->mtx); -} - /** POSIX interface **/ /* open a directory */ -DIR * ctru_opendir(const char *dirname) +DIR * console_opendir(const char *dirname) { logf("opendir(dirname=\"%s\")\n", dirname); @@ -161,7 +153,7 @@ DIR * ctru_opendir(const char *dirname) dir->stream.size = 0; dir->stream.flags = 0; - /* we will use file path to implement ctru_samedir function */ + /* we will use file path to implement console_samedir function */ strcpy(dir->stream.path, dirname); dirp = (DIR *)dir; @@ -175,7 +167,7 @@ file_error: } /* close a directory stream */ -int ctru_closedir(DIR *dirp) +int console_closedir(DIR *dirp) { int rc; @@ -183,14 +175,14 @@ int ctru_closedir(DIR *dirp) /* needs to work even if marked "nonexistant" */ struct dirstr_desc * const dir = (struct dirstr_desc *)dirp; - if (!PTR_IN_ARRAY(open_streams, dir, MAX_OPEN_DIRS)) + if (!PTR_IN_ARRAY(open_dir_streams, dir, MAX_OPEN_DIRS)) FILE_ERROR(EFAULT, -1); logf("closedir(dirname=\"%s\")\n", dir->stream.path); if (dir->stream.cache == NULL) { - logf("dir #%d: dir not open\n", (int)(dir - open_streams)); + logf("dir #%d: dir not open\n", (int)(dir - open_dir_streams)); FILE_ERROR(EBADF, -2); } @@ -236,7 +228,7 @@ void dirstr_entry_init(FS_DirectoryEntry *dirEntry, struct dirent *entry) } /* read a directory */ -struct dirent * ctru_readdir(DIR *dirp) +struct dirent * console_readdir(DIR *dirp) { struct dirstr_desc * const dir = GET_DIRSTR(READER, dirp); if (!dir) @@ -294,7 +286,7 @@ enum }; /* make a directory */ -int ctru_mkdir(const char *path) +int console_mkdir(const char *path) { logf("mkdir(path=\"%s\")\n", path); @@ -321,7 +313,7 @@ file_error: } /* remove a directory */ -int ctru_rmdir(const char *name) +int console_rmdir(const char *name) { logf("rmdir(name=\"%s\")\n", name); @@ -355,7 +347,7 @@ file_error: /** Extended interface **/ /* return if two directory streams refer to the same directory */ -int ctru_samedir(DIR *dirp1, DIR *dirp2) +int console_samedir(DIR *dirp1, DIR *dirp2) { struct dirstr_desc * const dir1 = GET_DIRSTR(WRITER, dirp1); if (!dir1) @@ -373,7 +365,7 @@ int ctru_samedir(DIR *dirp1, DIR *dirp2) } /* test directory existence (returns 'false' if a file) */ -bool ctru_dir_exists(const char *dirname) +bool console_dir_exists(const char *dirname) { file_internal_lock_WRITER(); @@ -398,7 +390,7 @@ file_error: } /* get the portable info from the native entry */ -struct dirinfo dir_get_info(DIR *dirp, struct dirent *entry) +struct dirinfo console_dir_get_info(DIR *dirp, struct dirent *entry) { int rc; if (!dirp || !entry) @@ -421,7 +413,7 @@ file_error: return (struct dirinfo){ .attribute = 0 }; } -const char* ctru_root_realpath(void) +const char* console_root_realpath(void) { /* Native only, for APP and SIM see respective filesystem-.c files */ return PATH_ROOTSTR; /* rb_namespace.c */ diff --git a/firmware/target/hosted/ctru/lib/sys_file.h b/firmware/target/hosted/game_console/lib/sys_file.h similarity index 87% rename from firmware/target/hosted/ctru/lib/sys_file.h rename to firmware/target/hosted/game_console/lib/sys_file.h index 4640b16db4..925b014716 100644 --- a/firmware/target/hosted/ctru/lib/sys_file.h +++ b/firmware/target/hosted/game_console/lib/sys_file.h @@ -27,27 +27,24 @@ #define strlcpy_from_os strlcpy -#define AtomicLoad(ptr) __atomic_load_n((u32*)(ptr), __ATOMIC_SEQ_CST) -#define AtomicAdd(ptr, value) __atomic_add_fetch((u32*)(ptr), value, __ATOMIC_SEQ_CST) - struct filestr_base { - PageReader* cache; /* buffer IO implementation (cache) */ - Handle handle; /* file handle */ - u64 size; /* file size */ - int flags; /* stream flags */ - char path[MAX_PATH+1]; + PageReader* cache; /* buffer IO implementation (cache) */ + sys_handle_type handle; /* file handle */ + u64 size; /* file size */ + int flags; /* stream flags */ + char path[MAX_PATH+1]; - LightLock mtx; /* serialization for this stream */ + sysMutex mtx; /* serialization for this stream */ }; static inline void filestr_lock(struct filestr_base *stream) { - LightLock_Lock(&stream->mtx); + sys_mutex_lock(&stream->mtx); } static inline void filestr_unlock(struct filestr_base *stream) { - LightLock_Unlock(&stream->mtx); + sys_mutex_unlock(&stream->mtx); } /* stream lock doesn't have to be used if getting RW lock writer access */ diff --git a/firmware/target/hosted/ctru/lib/sys_file.c b/firmware/target/hosted/game_console/lib/sys_file_3ds.c similarity index 50% rename from firmware/target/hosted/ctru/lib/sys_file.c rename to firmware/target/hosted/game_console/lib/sys_file_3ds.c index 72ebfa8fcd..924572ef0d 100644 --- a/firmware/target/hosted/ctru/lib/sys_file.c +++ b/firmware/target/hosted/game_console/lib/sys_file_3ds.c @@ -26,6 +26,7 @@ /* this includes a couple of 3ds headers */ #include "sys_file.h" +#include "sys_file_internal.h" #define RB_FILESYSTEM_OS #include "config.h" @@ -40,77 +41,8 @@ // #define LOGF_ENABLE #include "logf.h" -/** - * These functions provide a roughly POSIX-compatible file I/O API. - * Important: the bufferio I/O library (bfile) used in the 3ds does not work - * with write-only files due to an internal limitation. - * So all files will be opened with the read flag by default. - */ - -/* structure used for open file descriptors */ -static struct filestr_desc -{ - struct filestr_base stream; /* basic stream info (first!) */ - file_size_t offset; /* current offset for stream */ - u64 *sizep; /* shortcut to file size in fileobj */ -} open_streams[MAX_OPEN_FILES] = -{ - [0 ... MAX_OPEN_FILES-1] = { .stream = { .cache = NULL, .flags = 0 } } -}; - extern FS_Archive sdmcArchive; -/* check and return a struct filestr_desc* from a file descriptor number */ -static struct filestr_desc * get_filestr(int fildes) -{ - struct filestr_desc *file = &open_streams[fildes]; - - if ((unsigned int)fildes >= MAX_OPEN_FILES) - file = NULL; - else if (file->stream.cache != NULL) - return file; - - logf("fildes %d: bad file number\n", fildes); - errno = (file && (file->stream.cache == NULL)) ? ENXIO : EBADF; - return NULL; -} - -#define GET_FILESTR(type, fildes) \ - ({ \ - file_internal_lock_##type(); \ - struct filestr_desc * _file = get_filestr(fildes); \ - if (_file) \ - FILESTR_LOCK(type, &_file->stream); \ - else { \ - file_internal_unlock_##type(); \ - }\ - _file; \ - }) - -/* release the lock on the filestr_desc* */ -#define RELEASE_FILESTR(type, file) \ - ({ \ - FILESTR_UNLOCK(type, &(file)->stream); \ - file_internal_unlock_##type(); \ - }) - -/* find a free file descriptor */ -static int alloc_filestr(struct filestr_desc **filep) -{ - for (int fildes = 0; fildes < MAX_OPEN_FILES; fildes++) - { - struct filestr_desc *file = &open_streams[fildes]; - if (file->stream.cache == NULL) - { - *filep = file; - return fildes; - } - } - - logf("Too many files open\n"); - return -1; -} - /* check for file existence */ int test_stream_exists_internal(const char *path) { @@ -148,146 +80,6 @@ file_error: return rc; } -/* set the file pointer */ -static off_t lseek_internal(struct filestr_desc *file, off_t offset, - int whence) -{ - off_t rc; - off_t pos; - - off_t size = MIN(*file->sizep, FILE_SIZE_MAX); - off_t file_offset = AtomicLoad(&file->offset); - - switch (whence) - { - case SEEK_SET: - if (offset < 0 || (off_t)offset > size) - FILE_ERROR(EINVAL, -1); - - pos = offset; - break; - - case SEEK_CUR: - if ((offset < 0 && (off_t)-offset > file_offset) || - (offset > 0 && (off_t)offset > size - file_offset)) - FILE_ERROR(EINVAL, -1); - - pos = file_offset + offset; - break; - - case SEEK_END: - if (offset > 0 || (off_t)-offset > size) - FILE_ERROR(EINVAL, -1); - - pos = size + offset; - break; - - default: - FILE_ERROR(EINVAL, -1); - } - - AtomicSwap(&file->offset, pos); - - return pos; -file_error: - return rc; -} - -/* read from or write to the file; back end to read() and write() */ -static ssize_t readwrite(struct filestr_desc *file, void *buf, size_t nbyte, - bool write) -{ -#ifndef LOGF_ENABLE /* wipes out log before you can save it */ - /* DEBUGF("readwrite(%p,%lx,%lu,%s)\n", - file, (long)buf, (unsigned long)nbyte, write ? "write" : "read"); */ -#endif - - const file_size_t size = *file->sizep; - size_t filerem; - - if (write) - { - /* if opened in append mode, move pointer to end */ - if (file->stream.flags & O_APPEND) - AtomicSwap(&file->offset, MIN(size, FILE_SIZE_MAX)); - - filerem = FILE_SIZE_MAX - AtomicLoad(&file->offset); - } - else - { - /* limit to maximum possible offset (EOF or FILE_SIZE_MAX) */ - filerem = MIN(size, FILE_SIZE_MAX) - AtomicLoad(&file->offset); - } - - if (nbyte > filerem) - { - nbyte = filerem; - if (nbyte > 0) - {} - else if (write) - FILE_ERROR_RETURN(EFBIG, -1); /* would get too large */ - else if (AtomicLoad(&file->offset) >= FILE_SIZE_MAX) - FILE_ERROR_RETURN(EOVERFLOW, -2); /* can't read here */ - } - - if (nbyte == 0) - return 0; - - int rc = 0; - int_error_t n_err; - - if (write) { - n_err = PageReader_WriteAt(file->stream.cache, - buf, - nbyte, - AtomicLoad(&file->offset), - file->stream.flags & O_RDWR ? true : false); - } - else { - n_err = PageReader_ReadAt(file->stream.cache, - buf, - nbyte, - AtomicLoad(&file->offset)); - } - - if ((n_err.err != NULL) && strcmp(n_err.err, "io.EOF")) { - FILE_ERROR(ERRNO, -3); - } - -file_error:; -#ifdef DEBUG - if (errno == ENOSPC) - logf("No space left on device\n"); -#endif - - size_t done = n_err.n; - if (done) - { - /* error or not, update the file offset and size if anything was - transferred */ - AtomicAdd(&file->offset, done); -#ifndef LOGF_ENABLE /* wipes out log before you can save it */ - /* DEBUGF("file offset: %lld\n", file->offset); */ -#endif - /* adjust file size to length written */ - if (write && AtomicLoad(&file->offset) > size) - *file->sizep = AtomicLoad(&file->offset); - - return done; - } - - return rc; -} - -/* initialize the base descriptor */ -static void filestr_base_init(struct filestr_base *stream) -{ - stream->cache = NULL; - stream->handle = 0; - stream->size = 0; - LightLock_Init(&stream->mtx); -} - int open_internal_inner2(Handle *handle, const char *path, u32 openFlags, u32 attributes) { int rc; @@ -305,7 +97,7 @@ file_error: return rc; } -static int open_internal_inner1(const char *path, int oflag) +int open_internal_inner1(const char *path, int oflag) { int rc; struct filestr_desc *file; @@ -391,9 +183,9 @@ static int open_internal_inner1(const char *path, int oflag) file->stream.flags = oflag; file->stream.size = size; file->sizep = &file->stream.size; - AtomicSwap(&file->offset, 0); + AtomicSet(&file->offset, 0); - /* we will use file path to implement ctru_fsamefile function */ + /* we will use file path to implement console_fsamefile function */ strcpy(file->stream.path, path); return fildes; @@ -413,27 +205,7 @@ file_error: return rc; } -static int open_internal_locked(const char *path, int oflag) -{ - file_internal_lock_WRITER(); - int rc = open_internal_inner1(path, oflag); - file_internal_unlock_WRITER(); - return rc; -} - -int ctru_open(const char *path, int oflag, ...) -{ - logf("open(path=\"%s\",oflag=%X)\n", path, (unsigned)oflag); - return open_internal_locked(path, oflag); -} - -int ctru_creat(const char *path, mode_t mode) -{ - logf("creat(path=\"%s\")\n", path); - return ctru_open(path, O_WRONLY|O_CREAT|O_TRUNC, mode); -} - -int ctru_close(int fildes) +int console_close(int fildes) { logf("close(fd=%d)\n", fildes); @@ -466,7 +238,7 @@ file_error: } /* truncate a file to a specified length */ -int ctru_ftruncate(int fildes, off_t length) +int console_ftruncate(int fildes, off_t length) { logf("ftruncate(fd=%d,len=%ld)\n", fildes, (long)length); @@ -502,7 +274,7 @@ file_error: } /* synchronize changes to a file */ -int ctru_fsync(int fildes) +int console_fsync(int fildes) { logf("fsync(fd=%d)\n", fildes); @@ -530,79 +302,8 @@ file_error: return rc; } -/* move the read/write file offset */ -off_t ctru_lseek(int fildes, off_t offset, int whence) -{ -#ifndef LOGF_ENABLE /* wipes out log before you can save it */ - /* DEBUGF("lseek(fd=%d,ofs=%ld,wh=%d)\n", fildes, (long)offset, whence); */ -#endif - struct filestr_desc * const file = GET_FILESTR(READER, fildes); - if (!file) - FILE_ERROR_RETURN(ERRNO, -1); - - off_t rc = lseek_internal(file, offset, whence); - if (rc < 0) - FILE_ERROR(ERRNO, rc * 10 - 2); - -file_error: - RELEASE_FILESTR(READER, file); - return rc; -} - -/* read from a file */ -ssize_t ctru_read(int fildes, void *buf, size_t nbyte) -{ - struct filestr_desc * const file = GET_FILESTR(READER, fildes); - if (!file) - FILE_ERROR_RETURN(ERRNO, -1); - - ssize_t rc; - - if (file->stream.flags & O_WRONLY) - { - logf("read(fd=%d,buf=%p,nb=%lu) - " - "descriptor is write-only mode\n", - fildes, buf, (unsigned long)nbyte); - FILE_ERROR(EBADF, -2); - } - - rc = readwrite(file, buf, nbyte, false); - if (rc < 0) - FILE_ERROR(ERRNO, rc * 10 - 3); - -file_error: - RELEASE_FILESTR(READER, file); - return rc; -} - -/* write on a file */ -ssize_t ctru_write(int fildes, const void *buf, size_t nbyte) -{ - struct filestr_desc * const file = GET_FILESTR(READER, fildes); - if (!file) - FILE_ERROR_RETURN(ERRNO, -1); - - ssize_t rc; - - if (file->stream.flags & O_RDONLY) - { - logf("write(fd=%d,buf=%p,nb=%lu) - " - "descriptor is read-only mode\n", - fildes, buf, (unsigned long)nbyte); - FILE_ERROR(EBADF, -2); - } - - rc = readwrite(file, (void *)buf, nbyte, true); - if (rc < 0) - FILE_ERROR(ERRNO, rc * 10 - 3); - -file_error: - RELEASE_FILESTR(READER, file); - return rc; -} - /* remove a file */ -int ctru_remove(const char *path) +int console_remove(const char *path) { logf("remove(path=\"%s\")\n", path); @@ -621,7 +322,7 @@ file_error: } /* rename a file */ -int ctru_rename(const char *old, const char *new) +int console_rename(const char *old, const char *new) { /* note: move by rename does not work in devkitARM toolchain */ logf("rename(old=\"%s\",new=\"%s\")\n", old, new); @@ -698,7 +399,7 @@ file_error: /** Extensions **/ /* todo: utime does not work in devkitARM toolchain */ -int ctru_modtime(const char *path, time_t modtime) +int console_modtime(const char *path, time_t modtime) { struct utimbuf times = { @@ -709,66 +410,8 @@ int ctru_modtime(const char *path, time_t modtime) return utime(path, ×); } -/* get the binary size of a file (in bytes) */ -off_t ctru_filesize(int fildes) -{ - struct filestr_desc * const file = GET_FILESTR(READER, fildes); - if (!file) - FILE_ERROR_RETURN(ERRNO, -1); - - off_t rc; - file_size_t size = *file->sizep; - - if (size > FILE_SIZE_MAX) - FILE_ERROR(EOVERFLOW, -2); - - rc = (off_t)size; -file_error: - RELEASE_FILESTR(READER, file); - return rc; -} - -/* test if two file descriptors refer to the same file */ -int ctru_fsamefile(int fildes1, int fildes2) -{ - struct filestr_desc * const file1 = GET_FILESTR(WRITER, fildes1); - if (!file1) - FILE_ERROR_RETURN(ERRNO, -1); - - int rc = -2; - - struct filestr_desc * const file2 = get_filestr(fildes2); - if (file2) - rc = strcmp(file1->stream.path, file2->stream.path) == 0 ? 1 : 0; - - RELEASE_FILESTR(WRITER, file1); - return rc; -} - -/* tell the relationship of path1 to path2 */ -int ctru_relate(const char *path1, const char *path2) -{ - /* FAT32 file system does not support symbolic links, - therefore, comparing the two full paths should be enough - to tell relationship */ - logf("relate(path1=\"%s\",path2=\"%s\")\n", path1, path2); - int rc = RELATE_DIFFERENT; - if (strcmp(path1, path2) == 0) - rc = RELATE_SAME; - return rc; -} - -/* test file or directory existence */ -bool ctru_file_exists(const char *path) -{ - file_internal_lock_WRITER(); - bool rc = test_stream_exists_internal(path) > 0; - file_internal_unlock_WRITER(); - return rc; -} - /* note: no symbolic links support in devkitARM */ -ssize_t ctru_readlink(const char *path, char *buf, size_t bufsiz) +ssize_t console_readlink(const char *path, char *buf, size_t bufsiz) { return readlink(path, buf, bufsiz); } diff --git a/firmware/target/hosted/game_console/lib/sys_file_common.c b/firmware/target/hosted/game_console/lib/sys_file_common.c new file mode 100644 index 0000000000..481d0fc9a5 --- /dev/null +++ b/firmware/target/hosted/game_console/lib/sys_file_common.c @@ -0,0 +1,384 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2025 Mauricio G. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#include +#include +#include +#include +#include +#include "fs_defines.h" + +/* this includes a couple of 3ds headers */ +#include "sys_file.h" +#include "sys_file_internal.h" + +#define RB_FILESYSTEM_OS +#include "config.h" +#include "system.h" +#include "file.h" +#include "debug.h" +#include "string-extra.h" + +/* This file is based on firmware/common/file.c */ + +/* Define LOGF_ENABLE to enable logf output in this file */ +// #define LOGF_ENABLE +#include "logf.h" + +/** + * These functions provide a roughly POSIX-compatible file I/O API. + * Important: the bufferio I/O library (bfile) used in the 3ds does not work + * with write-only files due to an internal limitation. + * So all files will be opened with the read flag by default. + */ + +struct filestr_desc open_streams[MAX_OPEN_FILES] = +{ + [0 ... MAX_OPEN_FILES-1] = { .stream = { .cache = NULL, .flags = 0 } } +}; + +/* check and return a struct filestr_desc* from a file descriptor number */ +struct filestr_desc * get_filestr(int fildes) +{ + struct filestr_desc *file = &open_streams[fildes]; + + if ((unsigned int)fildes >= MAX_OPEN_FILES) + file = NULL; + else if (file->stream.cache != NULL) + return file; + + logf("fildes %d: bad file number\n", fildes); + errno = (file && (file->stream.cache == NULL)) ? ENXIO : EBADF; + return NULL; +} + +/* find a free file descriptor */ +int alloc_filestr(struct filestr_desc **filep) +{ + for (int fildes = 0; fildes < MAX_OPEN_FILES; fildes++) + { + struct filestr_desc *file = &open_streams[fildes]; + if (file->stream.cache == NULL) + { + *filep = file; + return fildes; + } + } + + logf("Too many files open\n"); + return -1; +} + +/* set the file pointer */ +off_t lseek_internal(struct filestr_desc *file, off_t offset, + int whence) +{ + off_t rc; + off_t pos; + + off_t size = MIN(*file->sizep, FILE_SIZE_MAX); + off_t file_offset = AtomicGet(&file->offset); + + switch (whence) + { + case SEEK_SET: + if (offset < 0 || (off_t)offset > size) + FILE_ERROR(EINVAL, -1); + + pos = offset; + break; + + case SEEK_CUR: + if ((offset < 0 && (off_t)-offset > file_offset) || + (offset > 0 && (off_t)offset > size - file_offset)) + FILE_ERROR(EINVAL, -1); + + pos = file_offset + offset; + break; + + case SEEK_END: + if (offset > 0 || (off_t)-offset > size) + FILE_ERROR(EINVAL, -1); + + pos = size + offset; + break; + + default: + FILE_ERROR(EINVAL, -1); + } + + AtomicSet(&file->offset, pos); + + return pos; +file_error: + return rc; +} + +/* read from or write to the file; back end to read() and write() */ +ssize_t readwrite(struct filestr_desc *file, void *buf, size_t nbyte, + bool write) +{ +#ifndef LOGF_ENABLE /* wipes out log before you can save it */ + /* DEBUGF("readwrite(%p,%lx,%lu,%s)\n", + file, (long)buf, (unsigned long)nbyte, write ? "write" : "read"); */ +#endif + + const file_size_t size = *file->sizep; + size_t filerem; + + if (write) + { + /* if opened in append mode, move pointer to end */ + if (file->stream.flags & O_APPEND) + AtomicSet(&file->offset, MIN(size, FILE_SIZE_MAX)); + + filerem = FILE_SIZE_MAX - AtomicGet(&file->offset); + } + else + { + /* limit to maximum possible offset (EOF or FILE_SIZE_MAX) */ + filerem = MIN(size, FILE_SIZE_MAX) - AtomicGet(&file->offset); + } + + if (nbyte > filerem) + { + nbyte = filerem; + if (nbyte > 0) + {} + else if (write) + FILE_ERROR_RETURN(EFBIG, -1); /* would get too large */ + else if (AtomicGet(&file->offset) >= FILE_SIZE_MAX) + FILE_ERROR_RETURN(EOVERFLOW, -2); /* can't read here */ + } + + if (nbyte == 0) + return 0; + + int rc = 0; + int_error_t n_err; + + if (write) { + n_err = PageReader_WriteAt(file->stream.cache, + buf, + nbyte, + AtomicGet(&file->offset), + file->stream.flags & O_RDWR ? true : false); + } + else { + n_err = PageReader_ReadAt(file->stream.cache, + buf, + nbyte, + AtomicGet(&file->offset)); + } + + if ((n_err.err != NULL) && strcmp(n_err.err, "io.EOF")) { + FILE_ERROR(ERRNO, -3); + } + +file_error:; +#ifdef DEBUG + if (errno == ENOSPC) + logf("No space left on device\n"); +#endif + + size_t done = n_err.n; + if (done) + { + /* error or not, update the file offset and size if anything was + transferred */ + AtomicAdd(&file->offset, done); +#ifndef LOGF_ENABLE /* wipes out log before you can save it */ + /* DEBUGF("file offset: %lld\n", file->offset); */ +#endif + /* adjust file size to length written */ + if (write && AtomicGet(&file->offset) > size) + *file->sizep = AtomicGet(&file->offset); + + return done; + } + + return rc; +} + +/* initialize the base descriptor */ +void filestr_base_init(struct filestr_base *stream) +{ + stream->cache = NULL; + stream->handle = 0; + stream->size = 0; + sys_mutex_init(&stream->mtx); +} + +int open_internal_inner1(const char *path, int oflag); +int open_internal_locked(const char *path, int oflag) +{ + file_internal_lock_WRITER(); + int rc = open_internal_inner1(path, oflag); + file_internal_unlock_WRITER(); + return rc; +} + +int console_open(const char *path, int oflag, ...) +{ + logf("open(path=\"%s\",oflag=%X)\n", path, (unsigned)oflag); + + /* we need to wait a little here when playing a folder with many small files + to prevent other threads starvation */ + sleep(10); + + return open_internal_locked(path, oflag); +} + +int console_creat(const char *path, mode_t mode) +{ + logf("creat(path=\"%s\")\n", path); + return console_open(path, O_WRONLY|O_CREAT|O_TRUNC, mode); +} + +/* move the read/write file offset */ +off_t console_lseek(int fildes, off_t offset, int whence) +{ +#ifndef LOGF_ENABLE /* wipes out log before you can save it */ + /* DEBUGF("lseek(fd=%d,ofs=%ld,wh=%d)\n", fildes, (long)offset, whence); */ +#endif + struct filestr_desc * const file = GET_FILESTR(READER, fildes); + if (!file) + FILE_ERROR_RETURN(ERRNO, -1); + + off_t rc = lseek_internal(file, offset, whence); + if (rc < 0) + FILE_ERROR(ERRNO, rc * 10 - 2); + +file_error: + RELEASE_FILESTR(READER, file); + return rc; +} + +/* read from a file */ +ssize_t console_read(int fildes, void *buf, size_t nbyte) +{ + struct filestr_desc * const file = GET_FILESTR(READER, fildes); + if (!file) + FILE_ERROR_RETURN(ERRNO, -1); + + ssize_t rc; + + if (file->stream.flags & O_WRONLY) + { + logf("read(fd=%d,buf=%p,nb=%lu) - " + "descriptor is write-only mode\n", + fildes, buf, (unsigned long)nbyte); + FILE_ERROR(EBADF, -2); + } + + rc = readwrite(file, buf, nbyte, false); + if (rc < 0) + FILE_ERROR(ERRNO, rc * 10 - 3); + +file_error: + RELEASE_FILESTR(READER, file); + return rc; +} + +/* write on a file */ +ssize_t console_write(int fildes, const void *buf, size_t nbyte) +{ + struct filestr_desc * const file = GET_FILESTR(READER, fildes); + if (!file) + FILE_ERROR_RETURN(ERRNO, -1); + + ssize_t rc; + + if (file->stream.flags & O_RDONLY) + { + logf("write(fd=%d,buf=%p,nb=%lu) - " + "descriptor is read-only mode\n", + fildes, buf, (unsigned long)nbyte); + FILE_ERROR(EBADF, -2); + } + + rc = readwrite(file, (void *)buf, nbyte, true); + if (rc < 0) + FILE_ERROR(ERRNO, rc * 10 - 3); + +file_error: + RELEASE_FILESTR(READER, file); + return rc; +} + +/** Extensions **/ + +/* get the binary size of a file (in bytes) */ +off_t console_ffilesize(int fildes) +{ + struct filestr_desc * const file = GET_FILESTR(READER, fildes); + if (!file) + FILE_ERROR_RETURN(ERRNO, -1); + + off_t rc; + file_size_t size = *file->sizep; + + if (size > FILE_SIZE_MAX) + FILE_ERROR(EOVERFLOW, -2); + + rc = (off_t)size; +file_error: + RELEASE_FILESTR(READER, file); + return rc; +} + +/* test if two file descriptors refer to the same file */ +int console_fsamefile(int fildes1, int fildes2) +{ + struct filestr_desc * const file1 = GET_FILESTR(WRITER, fildes1); + if (!file1) + FILE_ERROR_RETURN(ERRNO, -1); + + int rc = -2; + + struct filestr_desc * const file2 = get_filestr(fildes2); + if (file2) + rc = strcmp(file1->stream.path, file2->stream.path) == 0 ? 1 : 0; + + RELEASE_FILESTR(WRITER, file1); + return rc; +} + +/* tell the relationship of path1 to path2 */ +int console_relate(const char *path1, const char *path2) +{ + /* FAT32 file system does not support symbolic links, + therefore, comparing the two full paths should be enough + to tell relationship */ + logf("relate(path1=\"%s\",path2=\"%s\")\n", path1, path2); + int rc = RELATE_DIFFERENT; + if (strcmp(path1, path2) == 0) + rc = RELATE_SAME; + return rc; +} + +/* test file or directory existence */ +int test_stream_exists_internal(const char *path); +bool console_file_exists(const char *path) +{ + file_internal_lock_WRITER(); + bool rc = test_stream_exists_internal(path) > 0; + file_internal_unlock_WRITER(); + return rc; +} diff --git a/firmware/target/hosted/game_console/lib/sys_file_internal.h b/firmware/target/hosted/game_console/lib/sys_file_internal.h new file mode 100644 index 0000000000..cb800c7dd9 --- /dev/null +++ b/firmware/target/hosted/game_console/lib/sys_file_internal.h @@ -0,0 +1,63 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2025 by Mauricio G. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#ifndef _SYS_FILE_INTERNAL_H +#define _SYS_FILE_INTERNAL_H + +/* structure used for open file descriptors */ +struct filestr_base; +struct filestr_desc +{ + struct filestr_base stream; /* basic stream info (first!) */ + file_size_t offset; /* current offset for stream */ + u64 *sizep; /* shortcut to file size in fileobj */ +}; + +extern struct filestr_desc open_streams[MAX_OPEN_FILES]; + +int test_stream_exists_internal(const char *path); +int alloc_filestr(struct filestr_desc **filep); +struct filestr_desc * get_filestr(int fildes); +off_t lseek_internal(struct filestr_desc *file, off_t offset, + int whence); +ssize_t readwrite(struct filestr_desc *file, void *buf, size_t nbyte, + bool write); +void filestr_base_init(struct filestr_base *stream); +int open_internal_locked(const char *path, int oflag); + +#define GET_FILESTR(type, fildes) \ + ({ \ + file_internal_lock_##type(); \ + struct filestr_desc * _file = get_filestr(fildes); \ + if (_file) \ + FILESTR_LOCK(type, &_file->stream); \ + else { \ + file_internal_unlock_##type(); \ + }\ + _file; \ + }) + +/* release the lock on the filestr_desc* */ +#define RELEASE_FILESTR(type, file) \ + ({ \ + FILESTR_UNLOCK(type, &(file)->stream); \ + file_internal_unlock_##type(); \ + }) +#endif /* _SYS_FILE_INTERNAL_H */ + diff --git a/firmware/target/hosted/ctru/lib/sys_thread.h b/firmware/target/hosted/game_console/lib/sys_thread.h similarity index 69% rename from firmware/target/hosted/ctru/lib/sys_thread.h rename to firmware/target/hosted/game_console/lib/sys_thread.h index d6dea134c5..e42c32faa4 100644 --- a/firmware/target/hosted/ctru/lib/sys_thread.h +++ b/firmware/target/hosted/game_console/lib/sys_thread.h @@ -21,20 +21,10 @@ #ifndef __SYSTHREAD_H__ #define __SYSTHREAD_H__ -#include <3ds/synchronization.h> -#include <3ds/thread.h> -#include <3ds/services/apt.h> +#include "sys_atomic.h" #include "thread.h" -/* Complementary atomic operations */ -bool _AtomicCAS(u32 *ptr, int oldval, int newval); -#define AtomicGet(ptr) __atomic_load_n((u32*)(ptr), __ATOMIC_SEQ_CST) -#define AtomicSet(ptr, value) AtomicSwap(ptr, value) -#define AtomicCAS(ptr, oldvalue, newvalue) _AtomicCAS((u32 *)(ptr), oldvalue, newvalue) -void AtomicLock(int *lock); -void AtomicUnlock(int *lock); - /* This code was taken from SDL2 thread implementation */ enum thread_state_t @@ -48,7 +38,7 @@ enum thread_state_t typedef struct _thread { int threadid; - Thread handle; + sys_thread_type handle; int status; int state; size_t stacksize; @@ -57,31 +47,52 @@ typedef struct _thread void *data; } sysThread; +typedef struct _mutex +{ + sys_mutex_type lock; +} sysMutex; + +typedef struct _sem +{ + sys_sem_type semaphore; +} sysSem; + typedef struct _cond { - RecursiveLock lock; + sysMutex lock; int waiting; int signals; - LightSemaphore wait_sem; - LightSemaphore wait_done; + sysSem wait_sem; + sysSem wait_done; } sysCond; -int sys_sem_wait(LightSemaphore *sem); -int sys_sem_wait_timeout(LightSemaphore *sem, u32 timeout); -int sys_sem_try_wait(LightSemaphore *sem); -u32 sys_sem_value(LightSemaphore *sem); +sysMutex *sys_mutex_create(void); +void sys_mutex_init(sysMutex *mutex); +int sys_mutex_lock(sysMutex *mutex); +int sys_mutex_try_lock(sysMutex *mutex); +int sys_mutex_unlock(sysMutex *mutex); -sysThread *sys_create_thread(int(*fn)(void *), const char *name, const size_t stacksize, +sysSem *sys_sem_create(u32 initial_value); +void sys_sem_init(sysSem *sem, u32 initial_value); +int sys_sem_wait(sysSem *sem); +int sys_sem_wait_timeout(sysSem *sem, u32 timeout); +int sys_sem_try_wait(sysSem *sem); +u32 sys_sem_value(sysSem *sem); +int sys_sem_post(sysSem *sem); + +sysCond *sys_cond_create(void); +void sys_cond_init(sysCond *cond); +void sys_cond_destroy(sysCond *cond); +int sys_cond_signal(sysCond *cond); +int sys_cond_broadcast(sysCond *cond); +int sys_cond_wait(sysCond *cond, sysMutex *mutex); + +sysThread *sys_thread_create(int(*fn)(void *), const char *name, const size_t stacksize, void *data IF_PRIO(, int priority) IF_COP(, unsigned int core)); void sys_run_thread(sysThread *thread); void sys_wait_thread(sysThread *thread, int *status); int sys_thread_id(void); int sys_set_thread_priority(sysThread *thread, int priority); -sysCond *sys_cond_create(void); -void sys_cond_destroy(sysCond *cond); -int sys_cond_signal(sysCond *cond); -int sys_cond_broadcast(sysCond *cond); -int sys_cond_wait(sysCond *cond, RecursiveLock *mutex); #endif /* #ifndef __SYSTHREAD_H__ */ diff --git a/firmware/target/hosted/ctru/lib/sys_thread.c b/firmware/target/hosted/game_console/lib/sys_thread_3ds.c similarity index 58% rename from firmware/target/hosted/ctru/lib/sys_thread.c rename to firmware/target/hosted/game_console/lib/sys_thread_3ds.c index 1634fb3d7f..d280aa39f2 100644 --- a/firmware/target/hosted/ctru/lib/sys_thread.c +++ b/firmware/target/hosted/game_console/lib/sys_thread_3ds.c @@ -28,49 +28,129 @@ #include "debug.h" #include "logf.h" -bool _AtomicCAS(u32 *ptr, int oldval, int newval) +/* sysMutex */ +sysMutex *sys_mutex_create(void) { - int expected = oldval; - int desired = newval; - return __atomic_compare_exchange(ptr, &expected, &desired, false, - __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); -} - -bool _AtomicTryLock(int *lock) -{ - int result; - asm volatile( - "ldrex %0, [%2] \n" - "teq %0, #0 \n" - "strexeq %0, %1, [%2] \n" - : "=&r"(result) - : "r"(1), "r"(lock) - : "cc", "memory" - ); - return result == 0; -} - -#define CPUPauseInstruction() asm volatile("yield" ::: "memory") -void AtomicLock(int *lock) -{ - int iterations = 0; - while (!_AtomicTryLock(lock)) { - if (iterations < 32) { - iterations++; - CPUPauseInstruction(); - } else { - sys_delay(0); - } + sysMutex *m = (sysMutex *) malloc(sizeof(sysMutex)); + if (m == NULL) { + DEBUGF("sys_create_mutex: no memory\n"); + return NULL; } + + sys_mutex_init(m); + return m; } -void AtomicUnlock(int *lock) +void sys_mutex_init(sysMutex *mutex) { - *lock = 0; + memset(mutex, 0, sizeof(sysMutex)); + RecursiveLock_Init(&mutex->lock); } +int sys_mutex_lock(sysMutex *mutex) +{ + RecursiveLock_Lock(&mutex->lock); + return 0; +} + +int sys_mutex_try_lock(sysMutex *mutex) +{ + int res = RecursiveLock_TryLock(&mutex->lock); + if (res < 0) { + logf("RecursiveLock_TryLock: %d", res); + return -1; + } + + return 0; +} + +int sys_mutex_unlock(sysMutex *mutex) +{ + RecursiveLock_Unlock(&mutex->lock); + return 0; +} + +/* sysSem */ +void sys_sem_init(sysSem *sem, u32 initial_value) +{ + LightSemaphore_Init(&sem->semaphore, initial_value, ((s16)0x7FFF)); +} + +sysSem *sys_sem_create(u32 initial_value) +{ + sysSem *s = (sysSem *) malloc(sizeof(sysSem)); + if (s == NULL) + { + DEBUGF("sys_create_sem: no memory\n"); + return NULL; + } + + sys_sem_init(s, initial_value); + return s; +} + +int wait_on_semaphore_for(sysSem *sem, u32 timeout) +{ + u64 stop_time = sys_get_ticks64() + timeout; + u64 current_time = sys_get_ticks64(); + while (current_time < stop_time) { + if (LightSemaphore_TryAcquire(&sem->semaphore, 1) == 0) { + return 0; + } + /* 100 microseconds seems to be the sweet spot */ + svcSleepThread(100000LL); + current_time = sys_get_ticks64(); + } + + /* If we failed, yield to avoid starvation on busy waits */ + svcSleepThread(1); + return 1; +} + +int sys_sem_try_wait(sysSem *sem) +{ + if (LightSemaphore_TryAcquire(&sem->semaphore, 1) != 0) { + /* If we failed, yield to avoid starvation on busy waits */ + svcSleepThread(1); + return 1; + } + + return 0; +} + +int sys_sem_wait_timeout(sysSem *sem, u32 timeout) +{ + if (timeout == (~(u32)0)) { + LightSemaphore_Acquire(&sem->semaphore, 1); + return 0; + } + + if (LightSemaphore_TryAcquire(&sem->semaphore, 1) != 0) { + return wait_on_semaphore_for(sem, timeout); + } + + return 0; +} + +int sys_sem_wait(sysSem *sem) +{ + return sys_sem_wait_timeout(sem, (~(u32)0)); +} + +u32 sys_sem_value(sysSem *sem) +{ + return sem->semaphore.current_count; +} + +int sys_sem_post(sysSem *sem) +{ + LightSemaphore_Release(&sem->semaphore, 1); + return 0; +} + +/* sysThread */ /* Convert rockbox priority value to libctru value */ -int get_ctru_thread_priority(int priority) +int get_3ds_thread_priority(int priority) { if ((priority == PRIORITY_REALTIME_1) || (priority == PRIORITY_REALTIME_2) || (priority == PRIORITY_REALTIME_3) || (priority == PRIORITY_REALTIME_4) || @@ -106,59 +186,6 @@ static void thread_entry(void *arg) threadExit(0); } -int wait_on_semaphore_for(LightSemaphore *sem, u32 timeout) -{ - u64 stop_time = sys_get_ticks64() + timeout; - u64 current_time = sys_get_ticks64(); - while (current_time < stop_time) { - if (LightSemaphore_TryAcquire(sem, 1) == 0) { - return 0; - } - /* 100 microseconds seems to be the sweet spot */ - svcSleepThread(100000LL); - current_time = sys_get_ticks64(); - } - - /* If we failed, yield to avoid starvation on busy waits */ - svcSleepThread(1); - return 1; -} - -int sys_sem_try_wait(LightSemaphore *sem) -{ - if (LightSemaphore_TryAcquire(sem, 1) != 0) { - /* If we failed, yield to avoid starvation on busy waits */ - svcSleepThread(1); - return 1; - } - - return 0; -} - -int sys_sem_wait_timeout(LightSemaphore *sem, u32 timeout) -{ - if (timeout == (~(u32)0)) { - LightSemaphore_Acquire(sem, 1); - return 0; - } - - if (LightSemaphore_TryAcquire(sem, 1) != 0) { - return wait_on_semaphore_for(sem, timeout); - } - - return 0; -} - -int sys_sem_wait(LightSemaphore *sem) -{ - return sys_sem_wait_timeout(sem, (~(u32)0)); -} - -u32 sys_sem_value(LightSemaphore *sem) -{ - return sem->current_count; -} - int sys_thread_id(void) { u32 thread_ID = 0; @@ -188,7 +215,7 @@ void sys_run_thread(sysThread *thread) } } -sysThread *sys_create_thread(int(*fn)(void *), const char *name, const size_t stacksize, +sysThread *sys_thread_create(int(*fn)(void *), const char *name, const size_t stacksize, void *data IF_PRIO(, int priority) IF_COP(, unsigned int core)) { sys_ticks_init(); @@ -216,7 +243,7 @@ sysThread *sys_create_thread(int(*fn)(void *), const char *name, const size_t st thread->handle = threadCreate(thread_entry, thread, get_thread_stack_size(stacksize), - get_ctru_thread_priority(priority), + get_3ds_thread_priority(priority), cpu, false); @@ -275,127 +302,10 @@ int sys_set_thread_priority(sysThread *thread, int priority) { Handle h = threadGetHandle(thread->handle); int old_priority = priority; - Result res = svcSetThreadPriority(h, get_ctru_thread_priority(priority)); + Result res = svcSetThreadPriority(h, get_3ds_thread_priority(priority)); if (R_SUCCEEDED(res)) { return priority; } return old_priority; -} - -/* sysCond */ -sysCond *sys_cond_create(void) -{ - sysCond *cond; - - cond = (sysCond *)malloc(sizeof(sysCond)); - if (cond) { - RecursiveLock_Init(&cond->lock); - LightSemaphore_Init(&cond->wait_sem, 0, ((s16)0x7FFF)); - LightSemaphore_Init(&cond->wait_done, 0, ((s16)0x7FFF)); - cond->waiting = cond->signals = 0; - } else { - DEBUGF("sys_cond_create: out of memory.\n");; - } - return cond; -} - -/* Destroy a condition variable */ -void sys_cond_destroy(sysCond *cond) -{ - if (cond) { - free(cond); - } -} - -/* Restart one of the threads that are waiting on the condition variable */ -int sys_cond_signal(sysCond *cond) -{ - if (!cond) { - DEBUGF("sys_cond_signal: Invalid param 'cond'\n"); - return -1; - } - - /* If there are waiting threads not already signalled, then - signal the condition and wait for the thread to respond. - */ - RecursiveLock_Lock(&cond->lock); - if (cond->waiting > cond->signals) { - ++cond->signals; - LightSemaphore_Release(&cond->wait_sem, 1); - RecursiveLock_Unlock(&cond->lock); - LightSemaphore_Acquire(&cond->wait_done, 1); - } else { - RecursiveLock_Unlock(&cond->lock); - } - - return 0; -} - -/* Restart all threads that are waiting on the condition variable */ -int sys_cond_broadcast(sysCond *cond) -{ - if (!cond) { - DEBUGF("sys_cond_signal: Invalid param 'cond'\n"); - return -1; - } - - /* If there are waiting threads not already signalled, then - signal the condition and wait for the thread to respond. - */ - RecursiveLock_Lock(&cond->lock); - if (cond->waiting > cond->signals) { - int i, num_waiting; - - num_waiting = (cond->waiting - cond->signals); - cond->signals = cond->waiting; - for (i = 0; i < num_waiting; ++i) { - LightSemaphore_Release(&cond->wait_sem, 1); - } - /* Now all released threads are blocked here, waiting for us. - Collect them all (and win fabulous prizes!) :-) - */ - RecursiveLock_Unlock(&cond->lock); - for (i = 0; i < num_waiting; ++i) { - LightSemaphore_Acquire(&cond->wait_done, 1); - } - } else { - RecursiveLock_Unlock(&cond->lock); - } - - return 0; -} - -int sys_cond_wait(sysCond *cond, RecursiveLock *mutex) -{ - if (!cond) { - DEBUGF("sys_cond_signal: Invalid param 'cond'\n"); - return -1; - } - - RecursiveLock_Lock(&cond->lock); - ++cond->waiting; - RecursiveLock_Unlock(&cond->lock); - - /* Unlock the mutex, as is required by condition variable semantics */ - RecursiveLock_Unlock(mutex); - - /* Wait for a signal */ - LightSemaphore_Acquire(&cond->wait_sem, 1); - - RecursiveLock_Lock(&cond->lock); - if (cond->signals > 0) { - /* We always notify the signal thread that we are done */ - LightSemaphore_Release(&cond->wait_done, 1); - - /* Signal handshake complete */ - --cond->signals; - } - --cond->waiting; - RecursiveLock_Unlock(&cond->lock); - - /* Lock the mutex, as is required by condition variable semantics */ - RecursiveLock_Lock(mutex); - - return 0; -} +} \ No newline at end of file diff --git a/firmware/target/hosted/ctru/lib/sys_timer.h b/firmware/target/hosted/game_console/lib/sys_timer.h similarity index 98% rename from firmware/target/hosted/ctru/lib/sys_timer.h rename to firmware/target/hosted/game_console/lib/sys_timer.h index 302fc216a2..564ec1bf4a 100644 --- a/firmware/target/hosted/ctru/lib/sys_timer.h +++ b/firmware/target/hosted/game_console/lib/sys_timer.h @@ -21,7 +21,6 @@ #ifndef __SYSTIMER_H__ #define __SYSTIMER_H__ -#include <3ds/svc.h> #include "sys_thread.h" typedef u32 (* timer_callback_ptr) (u32 interval, void *param); diff --git a/firmware/target/arm/s5l8702/ipodnano3g/cscodec-nano3g.c b/firmware/target/hosted/game_console/lib/sys_timer_3ds.c similarity index 53% rename from firmware/target/arm/s5l8702/ipodnano3g/cscodec-nano3g.c rename to firmware/target/hosted/game_console/lib/sys_timer_3ds.c index e3ebfc9525..8500567688 100644 --- a/firmware/target/arm/s5l8702/ipodnano3g/cscodec-nano3g.c +++ b/firmware/target/hosted/game_console/lib/sys_timer_3ds.c @@ -6,9 +6,7 @@ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * - * S5L8702-specific code for Cirrus codecs - * - * Copyright (c) 2010 Michael Sparmann + * Copyright (C) 2006 Dan Everton * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -20,48 +18,54 @@ * ****************************************************************************/ -#include "system.h" -#include "audiohw.h" -#include "i2c-s5l8702.h" -#include "s5l87xx.h" -#include "cscodec.h" +#include +#include +#include +#include +#include -void audiohw_init(void) +#include <3ds/os.h> + +#include "debug.h" +#include "logf.h" + +static bool ticks_started = false; +static u64 start_tick; + +#define NSEC_PER_MSEC 1000000ULL + +void sys_ticks_init(void) { -#ifdef HAVE_CS42L55 - audiohw_preinit(); -#endif + if (ticks_started) { + return; + } + ticks_started = true; + + start_tick = svcGetSystemTick(); } -unsigned char cscodec_read(int reg) +void sys_ticks_quit(void) { - // TODO: not tested - unsigned char data; - i2c_read(0, 0x94, reg, 1, &data); - return data; + ticks_started = false; } -void cscodec_write(int reg, unsigned char data) +u64 sys_get_ticks64(void) { - // XXX: not tested - i2c_write(0, 0x94, reg, 1, &data); + u64 elapsed; + if (!ticks_started) { + sys_ticks_init(); + } + + elapsed = svcGetSystemTick() - start_tick; + return elapsed / CPU_TICKS_PER_MSEC; } -void cscodec_power(bool state) +u32 sys_get_ticks(void) { - (void)state; //TODO: Figure out which LDO this is + return (u32)(sys_get_ticks64() & 0xFFFFFFFF); } -void cscodec_reset(bool state) +void sys_delay(u32 ms) { - // XXX: not tested - if (state) PDAT(3) &= ~8; - else PDAT(3) |= 8; -} - -void cscodec_clock(bool state) -{ - // XXX: not tested - if (state) CLKCON3 &= ~0xffff; - else CLKCON3 |= 0x8000; -} + svcSleepThread(ms * NSEC_PER_MSEC); +} \ No newline at end of file diff --git a/firmware/target/hosted/ctru/lib/sys_timer.c b/firmware/target/hosted/game_console/lib/sys_timer_common.c similarity index 84% rename from firmware/target/hosted/ctru/lib/sys_timer.c rename to firmware/target/hosted/game_console/lib/sys_timer_common.c index 8cc5fa334a..8fea61df21 100644 --- a/firmware/target/hosted/ctru/lib/sys_timer.c +++ b/firmware/target/hosted/game_console/lib/sys_timer_common.c @@ -24,8 +24,6 @@ #include #include -#include <3ds/os.h> -/* these include a couple of 3ds headers */ #include "sys_thread.h" #include "sys_timer.h" @@ -34,47 +32,7 @@ #define CACHELINE_SIZE 128 -static bool ticks_started = false; -static u64 start_tick; - -#define NSEC_PER_MSEC 1000000ULL - -void sys_ticks_init(void) -{ - if (ticks_started) { - return; - } - ticks_started = true; - - start_tick = svcGetSystemTick(); -} - -void sys_ticks_quit(void) -{ - ticks_started = false; -} - -u64 sys_get_ticks64(void) -{ - u64 elapsed; - if (!ticks_started) { - sys_ticks_init(); - } - - elapsed = svcGetSystemTick() - start_tick; - return elapsed / CPU_TICKS_PER_MSEC; -} - -u32 sys_get_ticks(void) -{ - return (u32)(sys_get_ticks64() & 0xFFFFFFFF); -} - -void sys_delay(u32 ms) -{ - svcSleepThread(ms * NSEC_PER_MSEC); -} - +/* sysTimer */ typedef struct _timer { int timerID; @@ -97,17 +55,17 @@ typedef struct _timer_map typedef struct { /* Data used by the main thread */ - Thread thread; + sysThread *thread; int nextID; timerMap *timermap; - RecursiveLock timermap_lock; + sysMutex *timermap_lock; /* Padding to separate cache lines between threads */ char cache_pad[CACHELINE_SIZE]; /* Data used to communicate with the timer thread */ int lock; - LightSemaphore sem; + sysSem sem; sysTimer *pending; sysTimer *freelist; int active; @@ -144,9 +102,9 @@ static void add_timer_interval(timerData *data, sysTimer *timer) timer->next = curr; } -static void timer_thread(void *_data) +static int timer_thread(void *_data) { - timerData *data = (timerData *)_data; + timerData *data = (timerData *) _data; sysTimer *pending; sysTimer *current; sysTimer *freelist_head = NULL; @@ -246,6 +204,8 @@ static void timer_thread(void *_data) */ sys_sem_wait_timeout(&data->sem, delay); } + + return 0; } int sys_timer_init(void) @@ -253,17 +213,22 @@ int sys_timer_init(void) timerData *data = &timer_data; if (!AtomicGet(&data->active)) { - RecursiveLock_Init(&data->timermap_lock); - LightSemaphore_Init(&data->sem, 0, ((s16)0x7FFF)); + data->timermap_lock = (sysMutex *) malloc(sizeof(sysMutex)); + if (!data->timermap_lock) { + return -1; + } + sys_mutex_init(data->timermap_lock); + sys_sem_init(&data->sem, 0); + AtomicSet(&data->active, 1); /* Timer threads use a callback into the app, so we can't set a limited stack size here. */ - data->thread = threadCreate(timer_thread, - data, - 32 * 1024, - 0x28, - -1, - false); + data->thread = sys_thread_create(timer_thread, + "timer", + 32 * 1024, /* 32 kB */ + data + IF_PRIO( , PRIORITY_REALTIME ) /* Realtime priority */ + IF_COP( , 0 )); if (!data->thread) { sys_timer_quit(); return -1; @@ -283,9 +248,8 @@ void sys_timer_quit(void) if (AtomicCAS(&data->active, 1, 0)) { /* active? Move to inactive. */ /* Shutdown the timer thread */ if (data->thread) { - LightSemaphore_Release(&data->sem, 1); - threadJoin(data->thread, U64_MAX); - threadFree(data->thread); + sys_sem_post(&data->sem); + sys_wait_thread(data->thread, NULL); data->thread = NULL; } @@ -305,6 +269,9 @@ void sys_timer_quit(void) data->timermap = entry->next; free(entry); } + + free(data->timermap_lock); + data->timermap_lock = NULL; } } @@ -353,10 +320,10 @@ int sys_add_timer(u32 interval, timer_callback_ptr callback, void *param) entry->timer = timer; entry->timerID = timer->timerID; - RecursiveLock_Lock(&data->timermap_lock); + sys_mutex_lock(data->timermap_lock); entry->next = data->timermap; data->timermap = entry; - RecursiveLock_Unlock(&data->timermap_lock); + sys_mutex_unlock(data->timermap_lock); /* Add the timer to the pending list for the timer thread */ AtomicLock(&data->lock); @@ -365,7 +332,7 @@ int sys_add_timer(u32 interval, timer_callback_ptr callback, void *param) AtomicUnlock(&data->lock); /* Wake up the timer thread if necessary */ - LightSemaphore_Release(&data->sem, 1); + sys_sem_post(&data->sem); return entry->timerID; } @@ -377,7 +344,7 @@ bool sys_remove_timer(int id) bool canceled = false; /* Find the timer */ - RecursiveLock_Lock(&data->timermap_lock); + sys_mutex_lock(data->timermap_lock); prev = NULL; for (entry = data->timermap; entry; prev = entry, entry = entry->next) { if (entry->timerID == id) { @@ -389,7 +356,7 @@ bool sys_remove_timer(int id) break; } } - RecursiveLock_Unlock(&data->timermap_lock); + sys_mutex_unlock(data->timermap_lock); if (entry) { if (!AtomicGet(&entry->timer->canceled)) { @@ -399,5 +366,4 @@ bool sys_remove_timer(int id) free(entry); } return canceled; -} - +} \ No newline at end of file diff --git a/firmware/target/hosted/game_console/lib/sys_types.h b/firmware/target/hosted/game_console/lib/sys_types.h new file mode 100644 index 0000000000..f62670bffe --- /dev/null +++ b/firmware/target/hosted/game_console/lib/sys_types.h @@ -0,0 +1,40 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2025 Mauricio G. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#ifndef __SYSTYPES_H__ +#define __SYSTYPES_H__ + +#ifdef N3DS +#include <3ds/svc.h> +#include <3ds/types.h> +#include <3ds/thread.h> +#include <3ds/result.h> +#include <3ds/services/apt.h> +#include <3ds/services/fs.h> +#include <3ds/synchronization.h> + +typedef Thread sys_thread_type; +typedef RecursiveLock sys_mutex_type; +typedef LightSemaphore sys_sem_type; +typedef Handle sys_handle_type; +#endif + +#endif /* __SYSTYPES_H__ */ + diff --git a/firmware/target/hosted/ctru/system-ctru.h b/firmware/target/hosted/game_console/system-console.h similarity index 88% rename from firmware/target/hosted/ctru/system-ctru.h rename to firmware/target/hosted/game_console/system-console.h index fb96af2912..abe27cd4b8 100644 --- a/firmware/target/hosted/ctru/system-ctru.h +++ b/firmware/target/hosted/game_console/system-console.h @@ -17,18 +17,10 @@ * KIND, either express or implied. * ****************************************************************************/ -#ifndef _SYSTEM_SDL_H_ -#define _SYSTEM_SDL_H_ +#ifndef _SYSTEM_CONSOLE_H_ +#define _SYSTEM_CONSOLE_H_ #include -#include - -#include <3ds/types.h> -#include <3ds/svc.h> -#include "sys_timer.h" -#ifdef BIT -#undef BIT -#endif #include "config.h" #include "gcc_extensions.h" @@ -56,11 +48,10 @@ int set_irq_level(int level); void sim_enter_irq_handler(void); void sim_exit_irq_handler(void); void sim_kernel_shutdown(void); -void sys_poweroff(void); void sys_handle_argv(int argc, char *argv[]); void gui_message_loop(void); void sim_do_exit(void) NORETURN_ATTR; -void sdl_sys_quit(void); +void console_sys_quit(void); void mcuhwc_init(void); void mcuhwc_close(void); @@ -70,4 +61,4 @@ extern bool showremote; extern double display_zoom; extern long start_tick; -#endif /* _SYSTEM_SDL_H_ */ +#endif /* _SYSTEM_CONSOLE_H_ */ diff --git a/firmware/target/hosted/ctru/system-target.h b/firmware/target/hosted/game_console/system-target.h similarity index 54% rename from firmware/target/hosted/ctru/system-target.h rename to firmware/target/hosted/game_console/system-target.h index 79df842405..6ec0f3ae7f 100644 --- a/firmware/target/hosted/ctru/system-target.h +++ b/firmware/target/hosted/game_console/system-target.h @@ -1,4 +1,4 @@ -#include "system-ctru.h" +#include "system-console.h" #define NEED_GENERIC_BYTESWAPS diff --git a/firmware/target/hosted/ctru/thread-ctru.c b/firmware/target/hosted/game_console/thread-console.c similarity index 82% rename from firmware/target/hosted/ctru/thread-ctru.c rename to firmware/target/hosted/game_console/thread-console.c index 6dc057e333..3be2c59024 100644 --- a/firmware/target/hosted/ctru/thread-ctru.c +++ b/firmware/target/hosted/game_console/thread-console.c @@ -5,8 +5,9 @@ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ + * $Id$ * - * Copyright (C) 2006 Dan Everton + * Copyright (C) 2026 Mauricio G. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -18,19 +19,19 @@ * ****************************************************************************/ -#include "autoconf.h" - #include #include #include #include /* memset() */ #include -#include "system-ctru.h" -#include "thread-ctru.h" -#include "sys_thread.h" +#include "system-console.h" +#include "thread-console.h" #include "../kernel-internal.h" #include "core_alloc.h" +#include "sys_thread.h" +#include "sys_timer.h" + /* Define this as 1 to show informational messages that are not errors. */ #define THREAD_DEBUGF_ENABLED 0 @@ -54,7 +55,7 @@ static jmp_buf thread_jmpbufs[MAXTHREADS]; /* this mutex locks out other Rockbox threads while one runs, * that enables us to simulate a cooperative environment even if * the host is preemptive */ -static RecursiveLock m; +static sysMutex *m; #define THREADS_RUN 0 #define THREADS_EXIT 1 #define THREADS_EXIT_COMMAND_DONE 2 @@ -78,7 +79,7 @@ void sim_thread_shutdown(void) threads_status = THREADS_EXIT; /* Take control */ - RecursiveLock_Lock(&m); + sys_mutex_lock(m); /* Signal all threads on delay or block */ for (i = 0; i < MAXTHREADS; i++) @@ -86,7 +87,7 @@ void sim_thread_shutdown(void) struct thread_entry *thread = __thread_slot_entry(i); if (thread->context.s == NULL) continue; - LightSemaphore_Release(thread->context.s, 1); + sys_sem_post(thread->context.s); } /* Wait for all threads to finish and cleanup old ones. */ @@ -97,11 +98,11 @@ void sim_thread_shutdown(void) if (t != NULL) { - RecursiveLock_Unlock(&m); + sys_mutex_unlock(m); /* Wait for it to finish */ sys_wait_thread(t, NULL); /* Relock for next thread signal */ - RecursiveLock_Lock(&m); + sys_mutex_lock(m); /* Already waited and exiting thread would have waited .told, * replacing it with t. */ thread->context.told = NULL; @@ -117,7 +118,7 @@ void sim_thread_shutdown(void) } } - RecursiveLock_Unlock(&m); + sys_mutex_unlock(m); /* Signal completion of operation */ threads_status = THREADS_EXIT_COMMAND_DONE; @@ -136,7 +137,7 @@ void sim_thread_exception_wait(void) /* A way to yield and leave the threading system for extended periods */ void sim_thread_lock(void *me) { - RecursiveLock_Lock(&m); + sys_mutex_lock(m); __running_self_entry() = (struct thread_entry *)me; if (threads_status != THREADS_RUN) @@ -146,7 +147,7 @@ void sim_thread_lock(void *me) void * sim_thread_unlock(void) { struct thread_entry *current = __running_self_entry(); - RecursiveLock_Unlock(&m); + sys_mutex_unlock(m); return current; } @@ -160,9 +161,9 @@ void switch_thread(void) { case STATE_RUNNING: { - RecursiveLock_Unlock(&m); + sys_mutex_unlock(m); /* Any other thread waiting already will get it first */ - RecursiveLock_Lock(&m); + sys_mutex_lock(m); break; } /* STATE_RUNNING: */ @@ -170,9 +171,9 @@ void switch_thread(void) { int oldlevel; - RecursiveLock_Unlock(&m); + sys_mutex_unlock(m); sys_sem_wait(current->context.s); - RecursiveLock_Lock(&m); + sys_mutex_lock(m); oldlevel = disable_irq_save(); current->state = STATE_RUNNING; @@ -184,9 +185,9 @@ void switch_thread(void) { int result, oldlevel; - RecursiveLock_Unlock(&m); + sys_mutex_unlock(m); result = sys_sem_wait_timeout(current->context.s, current->tmo_tick); - RecursiveLock_Lock(&m); + sys_mutex_lock(m); oldlevel = disable_irq_save(); @@ -207,9 +208,9 @@ void switch_thread(void) case STATE_SLEEPING: { - RecursiveLock_Unlock(&m); + sys_mutex_unlock(m); sys_sem_wait_timeout(current->context.s, current->tmo_tick); - RecursiveLock_Lock(&m); + sys_mutex_lock(m); current->state = STATE_RUNNING; break; } /* STATE_SLEEPING: */ @@ -252,7 +253,7 @@ void block_thread_(struct thread_entry *current, int ticks) } unsigned int wakeup_thread_(struct thread_entry *thread - IF_PRIO(, enum wakeup_thread_protocol proto)) + IF_PRIO(, enum wakeup_thread_protocol proto)) { switch (thread->state) { @@ -260,7 +261,7 @@ unsigned int wakeup_thread_(struct thread_entry *thread case STATE_BLOCKED_W_TMO: wait_queue_remove(thread); thread->state = STATE_RUNNING; - LightSemaphore_Release(thread->context.s, 1); + sys_sem_post(thread->context.s); return THREAD_OK; } @@ -275,15 +276,48 @@ void thread_thaw(unsigned int thread_id) if (thread->id == thread_id && thread->state == STATE_FROZEN) { thread->state = STATE_RUNNING; - LightSemaphore_Release(thread->context.s, 1); + sys_sem_post(thread->context.s); } } +#ifdef HAVE_SCHEDULER_BOOSTCTRL +/*--------------------------------------------------------------------------- + * Change the boost state of a thread boosting or unboosting the CPU + * as required. + *--------------------------------------------------------------------------- + */ +static inline void boost_thread(struct thread_entry *thread, bool boost) +{ + if ((thread->cpu_boost != 0) != boost) + { + thread->cpu_boost = boost; +#ifdef CPU_BOOST_LOGGING + const char fmt[] = __FILE__" thread[%s]"; + char pathbuf[sizeof(fmt) + 32]; /* thread name 32 */ + snprintf(pathbuf, sizeof(pathbuf), fmt, thread->name); + cpu_boost_(boost, pathbuf, __LINE__); +#else + cpu_boost(boost); +#endif + } +} + +void trigger_cpu_boost(void) +{ + boost_thread(__running_self_entry(), true); +} + +void cancel_cpu_boost(void) +{ + boost_thread(__running_self_entry(), false); +} +#endif + int runthread(void *data) { /* Cannot access thread variables before locking the mutex as the data structures may not be filled-in yet. */ - RecursiveLock_Lock(&m); + sys_mutex_lock(m); struct thread_entry *current = (struct thread_entry *)data; __running_self_entry() = current; @@ -296,18 +330,18 @@ int runthread(void *data) /* Run the thread routine */ if (current->state == STATE_FROZEN) { - RecursiveLock_Unlock(&m); + sys_mutex_unlock(m); sys_sem_wait(current->context.s); - RecursiveLock_Lock(&m); + sys_mutex_lock(m); __running_self_entry() = current; } if (threads_status == THREADS_RUN) { current->context.start(); - THREAD_DEBUGF("Thread Done: %d (%s)\n", - THREAD_ID_SLOT(current->id), - THREAD_GET_NAME(current)); + THREAD_DEBUGF("Thread Done: %ld (%s)\n", + THREAD_ID_SLOT(current->id), + THREAD_GET_NAME(current)); /* Thread routine returned - suicide */ } @@ -316,7 +350,7 @@ int runthread(void *data) else { /* Unlock and exit */ - RecursiveLock_Unlock(&m); + sys_mutex_unlock(m); } return 0; @@ -337,16 +371,14 @@ unsigned int create_thread(void (*function)(void), return 0; } - LightSemaphore *s = (LightSemaphore *) malloc(sizeof(LightSemaphore)); + sysSem *s = sys_sem_create(0); if (s == NULL) { DEBUGF("Failed to create semaphore\n"); return 0; } - LightSemaphore_Init(s, 0, 255); - - sysThread *t = sys_create_thread(runthread, + sysThread *t = sys_thread_create(runthread, name, stack_size, thread @@ -365,11 +397,10 @@ unsigned int create_thread(void (*function)(void), thread->context.start = function; thread->context.t = t; thread->context.s = s; - thread->priority = priority; THREAD_DEBUGF("New Thread: %lu (%s)\n", - (unsigned long)thread->id, - THREAD_GET_NAME(thread)); + (unsigned long)thread->id, + THREAD_GET_NAME(thread)); return thread->id; (void)stack; @@ -382,15 +413,14 @@ void thread_exit(void) int oldlevel = disable_irq_save(); sysThread *t = current->context.t; - LightSemaphore *s = current->context.s; + sysSem *s = current->context.s; - /* Wait the last thread here and keep this one or ctru will leak it since + /* Wait the last thread here and keep this one or the app will leak it since * it doesn't free its own library allocations unless a wait is performed. * Such behavior guards against the memory being invalid by the time * sys_wait_thread is reached and also against two different threads having * the same pointer. It also makes sys_wait_thread a non-concurrent function. * - * However, see more below about sys_kill_thread. */ sys_wait_thread(current->context.told, NULL); @@ -448,8 +478,18 @@ int thread_get_priority(unsigned int thread_id) /* Initialize threading */ void init_threads(void) { - RecursiveLock_Init(&m); - RecursiveLock_Lock(&m); + m = sys_mutex_create(); + if (m == NULL) + { + fprintf(stderr, "Failed to create mutex\n"); + return; + } + + if (sys_mutex_lock(m) == -1) + { + fprintf(stderr, "Couldn't lock mutex\n"); + return; + } thread_alloc_init(); @@ -461,12 +501,11 @@ void init_threads(void) } /* Slot 0 is reserved for the main thread - initialize it here and - then create the thread - it is possible to have a quick, early - shutdown try to access the structure. */ + then create the system thread - it is possible to have a quick, + early shutdown try to access the structure. */ thread->name = __main_thread_name; thread->state = STATE_RUNNING; - thread->context.s = (LightSemaphore *) malloc(sizeof(LightSemaphore)); - LightSemaphore_Init(thread->context.s, 0, 255); + thread->context.s = sys_sem_create(0); thread->context.t = NULL; /* NULL for the implicit main thread */ __running_self_entry() = thread; @@ -485,18 +524,19 @@ void init_threads(void) if (setjmp(thread_jmpbufs[THREAD_ID_SLOT(thread->id)]) == 0) { THREAD_DEBUGF("Main Thread: %lu (%s)\n", - (unsigned long)thread->id, - THREAD_GET_NAME(thread)); + (unsigned long)thread->id, + THREAD_GET_NAME(thread)); return; } - RecursiveLock_Unlock(&m); + sys_mutex_unlock(m); /* Set to 'COMMAND_DONE' when other rockbox threads have exited. */ while (threads_status < THREADS_EXIT_COMMAND_DONE) sys_delay(10); + free(m); + /* We're the main thead - perform exit - doesn't return. */ sim_do_exit(); } - diff --git a/firmware/target/hosted/ctru/thread-ctru.h b/firmware/target/hosted/game_console/thread-console.h similarity index 92% rename from firmware/target/hosted/ctru/thread-ctru.h rename to firmware/target/hosted/game_console/thread-console.h index 6f18576991..d9cd815a04 100644 --- a/firmware/target/hosted/ctru/thread-ctru.h +++ b/firmware/target/hosted/game_console/thread-console.h @@ -18,8 +18,8 @@ * ****************************************************************************/ -#ifndef __THREAD_CTRU_H__ -#define __THREAD_CTRU_H__ +#ifndef __THREAD_CONSOLE_H__ +#define __THREAD_CONSOLE_H__ /* extra thread functions that only apply when running on hosting platforms */ void sim_thread_lock(void *me); @@ -27,5 +27,5 @@ void * sim_thread_unlock(void); void sim_thread_exception_wait(void); void sim_thread_shutdown(void); /* Shut down all kernel threads gracefully */ -#endif /* #ifndef __THREAD_CTRU_H__ */ +#endif /* #ifndef __THREAD_CONSOLE_H__ */ diff --git a/firmware/target/hosted/ctru/timer-ctru.c b/firmware/target/hosted/game_console/timer-console.c similarity index 100% rename from firmware/target/hosted/ctru/timer-ctru.c rename to firmware/target/hosted/game_console/timer-console.c diff --git a/firmware/target/hosted/hiby/ap80max/button-ap80max.c b/firmware/target/hosted/hiby/ap80max/button-ap80max.c new file mode 100644 index 0000000000..1d61d4371d --- /dev/null +++ b/firmware/target/hosted/hiby/ap80max/button-ap80max.c @@ -0,0 +1,83 @@ +/*************************************************************************** + * __________ __ ___ + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2017 Marcin Bukat + * Copyright (C) 2025 by Melissa Autumn + * Copyright (C) 2026 by Jerry Shen + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#include + +#include "sysfs.h" +#include "button.h" +#include "button-target.h" +#include "hibylinux_codec.h" +#include "touchscreen.h" +#ifdef HAVE_BACKLIGHT +#include "backlight.h" +#endif /* HAVE_BACKLIGHT */ + +/* + * /dev/input: gpio keys, adc keys, ring encoder, touchscreen + */ + +int button_map(int keycode) +{ + switch(keycode) + { + case KEY_PLAYPAUSE: + return BUTTON_PLAY; + /* Keys are reversed as per glyphs */ + case KEY_PREVIOUSSONG: + return BUTTON_NEXT; + case KEY_NEXTSONG: + return BUTTON_PREV; + case KEY_POWER: + return BUTTON_POWER; + /* Scroll wheel */ + case KEY_LEFT: + return BUTTON_SCROLL_FWD; + case KEY_RIGHT: + return BUTTON_SCROLL_BACK; + + case BTN_TOUCH: + { +#ifdef HAVE_BACKLIGHT + if (is_backlight_on(true)) { + return BUTTON_TOUCH; + } + // Ignore + return 0; +#else + return BUTTON_TOUCH; +#endif + } + default: + return 0; + } +} + +bool headphones_inserted(void) +{ + #ifdef BOOTLOADER + int ps = 0; + #else + int ps = hiby_get_outputs(); + #endif + + return (ps == 2 || ps == 3); +} diff --git a/firmware/target/hosted/hiby/ap80max/button-target.h b/firmware/target/hosted/hiby/ap80max/button-target.h new file mode 100644 index 0000000000..3ee0aec152 --- /dev/null +++ b/firmware/target/hosted/hiby/ap80max/button-target.h @@ -0,0 +1,67 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2026 by Jerry Shen + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#ifndef _BUTTON_TARGET_H_ +#define _BUTTON_TARGET_H_ + +#include +#include "config.h" + +/* Main unit's buttons - four keys and a scroll wheel, no volume keys. */ + +#define BUTTON_POWER 0x00000001 +#define BUTTON_PREV 0x00000002 +#define BUTTON_NEXT 0x00000004 +#define BUTTON_PLAY 0x00000008 + +/* Scroll wheel */ +#define BUTTON_SCROLL_BACK 0x00000010 +#define BUTTON_SCROLL_FWD 0x00000020 + +#define BUTTON_MAIN 0x0000003f + +#define BUTTON_TOUCH 0x00000040 + +/* Touchscreen virtual buttons */ +#define BUTTON_TOPLEFT 0x00001000 +#define BUTTON_TOPMIDDLE 0x00002000 +#define BUTTON_TOPRIGHT 0x00004000 +#define BUTTON_MIDLEFT 0x00008000 +#define BUTTON_CENTER 0x00010000 +#define BUTTON_MIDRIGHT 0x00020000 +#define BUTTON_BOTTOMLEFT 0x00040000 +#define BUTTON_BOTTOMMIDDLE 0x00080000 +#define BUTTON_BOTTOMRIGHT 0x00100000 + +/* Software power-off */ +#define POWEROFF_BUTTON BUTTON_POWER +#define POWEROFF_COUNT 25 + +/* For the simulator/sdl plugins/xworld */ +#ifndef BOOTLOADER +#define BUTTON_LEFT BUTTON_PREV +#define BUTTON_RIGHT BUTTON_NEXT +#endif + +/* The left/right aliases above don't exist in BOOTLOADER builds */ +#define NO_BUTTON_LR + +int button_map(int keycode); + +#endif /* _BUTTON_TARGET_H_ */ diff --git a/firmware/target/hosted/hiby/ap80max/led-ap80max.c b/firmware/target/hosted/hiby/ap80max/led-ap80max.c new file mode 100644 index 0000000000..a272557a3d --- /dev/null +++ b/firmware/target/hosted/hiby/ap80max/led-ap80max.c @@ -0,0 +1,117 @@ +/*************************************************************************** + * __________ __ ___ + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2025 Melissa Autumn + * Copyright (C) 2026 by Jerry Shen + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include "config.h" +#include "backlight-target.h" +#include "sysfs.h" +#include "panic.h" +#include "lcd.h" +#include "settings.h" +#include "power.h" + +/* Same sgm31324 controller as the R3 Pro II, so the same sysfs node */ +static const char * const sysfs_led_type = +"/sys/class/leds/sgm31324-leds/led_pattern"; + +/* FIXME: taken from the R3 Pro II's leds_sgm31324_add.sh; this player ships + its own copy and the ordering has not been checked against it */ +enum AP80MAX_LED_PATTERNS { + LED_PATTERN_INVALID = -1, + LED_PATTERN_OFF = 0, + LED_PATTERN_LIGHT_BLUE, + LED_PATTERN_YELLOW, + LED_PATTERN_CYAN, + LED_PATTERN_ORANGE, + LED_PATTERN_UNK_1, /* This might do something, but it seems to just be off */ + LED_PATTERN_NORMAL_RED_BLINK, + LED_PATTERN_FAST_RED_BLINK, + LED_PATTERN_GREEN, + LED_PATTERN_BLUE, /* A slightly more blue blue */ + LED_PATTERN_PINK, + LED_PATTERN_WHITE, +}; + +int _last_buttonlight = LED_PATTERN_INVALID; + +void _set_pattern(enum AP80MAX_LED_PATTERNS pattern) { + if (_last_buttonlight != pattern) { + _last_buttonlight = pattern; + sysfs_set_int(sysfs_led_type, _last_buttonlight); + } +} + +#ifdef HAVE_GENERAL_PURPOSE_LED +void led_hw_brightness(int brightness) +{ + (void)brightness; +} + +void led_hw_charged(void) +{ + if (global_settings.use_led_indicators) { + _set_pattern(LED_PATTERN_GREEN); + } +} + +void led_hw_charging(void) +{ + _set_pattern(LED_PATTERN_NORMAL_RED_BLINK); +} + +void led_hw_off(void) +{ + // Don't turn the led off if charging. + if (charging_state()) + return; + + _set_pattern(LED_PATTERN_OFF); +} + +void led_hw_on(void) +{ + if (charging_state()) { + led_hw_charging(); + } else { + if (global_settings.use_led_indicators) { + _set_pattern(LED_PATTERN_LIGHT_BLUE); + } + else { + led_hw_off(); + } + } +} + +void led_hw_set_mode(bool mode) +{ + if (mode) { + led_hw_on(); + } + else { + led_hw_off(); + } +} +#endif diff --git a/firmware/target/hosted/hiby/ap80max/powermgmt-ap80max.c b/firmware/target/hosted/hiby/ap80max/powermgmt-ap80max.c new file mode 100644 index 0000000000..63e4255338 --- /dev/null +++ b/firmware/target/hosted/hiby/ap80max/powermgmt-ap80max.c @@ -0,0 +1,39 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2017 Marcin Bukat + * Copyright (C) 2025 by Melissa Autumn + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#include "powermgmt.h" +#include "power.h" + +unsigned short battery_level_disksafe = 3470; + +/* the OF shuts down at this voltage */ +unsigned short battery_level_shutoff = 3400; + +/* voltages (millivolt) of 0%, 10%, ... 100% when charging disabled */ +unsigned short percent_to_volt_discharge[11] = +{ + 3400, 3675, 3715, 3750, 3775, 3810, 3850, 4025, 4120, 4230, 4350 +}; + +/* voltages (millivolt) of 0%, 10%, ... 100% when charging enabled */ +unsigned short percent_to_volt_charge[11] = +{ + 3485, 3780, 3836, 3857, 3890, 3930, 3986, 4025, 4120, 4230, 4350 +}; diff --git a/firmware/target/hosted/hiby/hibylinux_codec.c b/firmware/target/hosted/hiby/hibylinux_codec.c index 0464dceaf2..4cb445887d 100644 --- a/firmware/target/hosted/hiby/hibylinux_codec.c +++ b/firmware/target/hosted/hiby/hibylinux_codec.c @@ -65,11 +65,11 @@ int hiby_has_valid_output(void) { const char * const sysfs_hs_switch = "/sys/class/switch/headset/state"; const char * const sysfs_bal_switch = "/sys/class/switch/balance/state"; - sysfs_get_int(sysfs_hs_switch, &status); - if (status) ps = 2; // headset + if (sysfs_get_int(sysfs_hs_switch, &status) && status) + ps = 2; // headset - sysfs_get_int(sysfs_bal_switch, &status); - if (status) ps = 3; // balanced output + if (sysfs_get_int(sysfs_bal_switch, &status) && status) + ps = 3; // balanced output return ps; } @@ -86,6 +86,12 @@ void hiby_set_output(int ps) { if (!hw_init || muted) return; + // Default to headset if nothing was ever inserted; otherwise, R3 Pro II crashes on playback + if (ps == 0) + { + ps = last_ps > 0 ? last_ps : 2; + } + if (last_ps != ps) { logf("set out %d/%d", ps, last_ps); diff --git a/firmware/target/hosted/hiby/lcd-target.h b/firmware/target/hosted/hiby/lcd-target.h index 4882fab60a..0dab04cc69 100644 --- a/firmware/target/hosted/hiby/lcd-target.h +++ b/firmware/target/hosted/hiby/lcd-target.h @@ -26,7 +26,13 @@ #define LCD_OPTIMIZED_UPDATE_RECT extern fb_data *framebuffer; + +#ifdef FB_STRIDE_MISMATCH +#define LCD_FRAMEBUF_ADDR(col, row) \ + (framebuffer + (row)*FB_STRIDE_MISMATCH + (col) + FB_STRIDE_XOFFSET) +#else #define LCD_FRAMEBUF_ADDR(col, row) (framebuffer + (row)*LCD_WIDTH + (col)) +#endif extern void lcd_set_active(bool active); #endif /* __LCD_TARGET_H__ */ diff --git a/firmware/target/hosted/hiby/usb-dac-hiby.c b/firmware/target/hosted/hiby/usb-dac-hiby.c new file mode 100644 index 0000000000..469dc640b9 --- /dev/null +++ b/firmware/target/hosted/hiby/usb-dac-hiby.c @@ -0,0 +1,220 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2026 by Michael McAllister + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +/* Host-PCM pump for the HiBy USB DAC gadget mode. + * + * The vendor kernel's UAC gadget function "uac_sa" exposes the char device + * /dev/uac_sa, which delivers the host's PCM: the isochronous OUT frames are + * converted to left-justified S16-in-S32 stereo and queued in a kernel ring, + * drained with read(). read() is non-blocking and returns -1 until at least + * the requested count (which must be <= the kernel ring/2) is buffered. + * + * A pump thread drains it into a small single-producer/single-consumer + * ring, the mixer's get_more callback hands that PCM to the CS43131 through + * the normal ALSA output path, the same way firmware/usbstack/usb_audio.c + * drives its host-PCM playback. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "kernel.h" +#include "pcm_mixer.h" +#include "pcm_sampr.h" +#include "system.h" +#include "thread.h" +#include "usb.h" +#include "usb-dac-hiby.h" + +/*#define LOGF_ENABLE*/ +#include "logf.h" + +#define UAC_SA_DEV "/dev/uac_sa" +/* ioctl: fills int[3] = { fmt, rate_hz, bits } */ +#define UAC_SA_GET_STATUS 1 +/* uac_sa delivers S32 stereo: 8 bytes per frame */ +#define UAC_SA_FRAME_SIZE 8 + +/* Ring of S16 stereo frames. A power of two so head/tail wrap cleanly. */ +#define DAC_RING_FRAMES 8192 /* ~186 ms at 44.1 kHz */ +#define DAC_CHUNK_FRAMES 512 /* mixer buffer granularity */ +/* one read()'s worth. Must stay <= the kernel ring/2 */ +#define DAC_READ_BYTES (DAC_CHUNK_FRAMES * UAC_SA_FRAME_SIZE) + +static int16_t dac_ring[DAC_RING_FRAMES * 2]; +static volatile unsigned int dac_head; /* frames produced; pump only */ +static volatile unsigned int dac_tail; /* frames consumed; mixer only */ +static pthread_t dac_thread; +static volatile bool dac_running; +static int dac_fd = -1; + +/* Mixer callback (PCM feed context). Returns the next chunk from the ring, + * zero-padded with silence on underrun so the stream never stalls. Double + * buffered so the returned pointer stays valid until the following call. */ +static void dac_get_more(const void **start, size_t *size) +{ + static int16_t out[2][DAC_CHUNK_FRAMES * 2]; + static int which; + int16_t *buf; + + which ^= 1; + buf = out[which]; + + unsigned int tail = dac_tail; + unsigned int avail = dac_head - tail; /* wrap-safe */ + avail = MIN(avail, DAC_CHUNK_FRAMES); + + for (unsigned int i = 0; i < avail; i++) + { + unsigned int idx = (tail + i) & (DAC_RING_FRAMES - 1); + buf[2 * i] = dac_ring[2 * idx]; + buf[2 * i + 1] = dac_ring[2 * idx + 1]; + } + memset(&buf[2 * avail], 0, + (DAC_CHUNK_FRAMES - avail) * 2 * sizeof(int16_t)); + dac_tail = tail + avail; + + *start = buf; + *size = DAC_CHUNK_FRAMES * 2 * sizeof(int16_t); +} + +/* Drain /dev/uac_sa into the ring. The driver's only file + * operations are open/read/ioctl (there is no poll()), so retry with a + * short sleep when the host has not queued a full chunk yet. + * + * This is a raw pthread, not a Rockbox thread, so it must use usleep() + * rather than the cooperative sleep() (which only the scheduler's own + * threads may call). */ +static void *dac_pump_thread(void *arg) +{ + int32_t rbuf[DAC_READ_BYTES / sizeof(int32_t)]; + (void)arg; + + while (dac_running) + { + unsigned int head = dac_head; + + /* Leave room for a full read; else wait for the mixer to drain */ + if (head - dac_tail > DAC_RING_FRAMES - DAC_CHUNK_FRAMES) + { + usleep(1000); + continue; + } + + ssize_t n = read(dac_fd, rbuf, DAC_READ_BYTES); + if (n <= 0) + { + usleep(1000); /* no host data queued yet */ + continue; + } + + int frames = n / UAC_SA_FRAME_SIZE; + for (int i = 0; i < frames; i++) + { + unsigned int idx = (head + i) & (DAC_RING_FRAMES - 1); + dac_ring[2 * idx] = (int16_t)(rbuf[2 * i] >> 16); + dac_ring[2 * idx + 1] = (int16_t)(rbuf[2 * i + 1] >> 16); + } + dac_head = head + frames; + } + + return NULL; +} + +bool usb_dac_start(void) +{ + static const struct mixer_play_cbs cbs = { .get_more = dac_get_more }; + + /* The node is created asynchronously when the UDC binds; during boot + * the hotplug helper can lag behind, so wait briefly for it. This runs + * on a Rockbox thread, so sleep() yields to the cooperative scheduler + * instead of stalling every other thread the way usleep() would. */ + for (int tries = 50; tries > 0; tries--) + { + dac_fd = open(UAC_SA_DEV, O_RDWR); + if (dac_fd >= 0) + break; + sleep(HZ / 50); /* 20 ms */ + } + if (dac_fd < 0) + { + logf("uac_sa open failed (%d)", errno); + return false; + } + + int rate = USB_DAC_SAMPLE_RATE; +#ifdef LOGF_ENABLE + // Both on Windows and Linux it always returns 44100 instead of requested rate (which indicates some generic issue). + // But since we hardcoded the only supported rate with USB_DAC_SAMPLE_RATE in gadget config (see usb-hiby-gadget.c), + // it should be safe to ignore the mismatch and just log it for debugging purposes. + int st[3] = {0, 0, 0}; + int status_ok = ioctl(dac_fd, UAC_SA_GET_STATUS, st); + int reported_rate = st[1]; + if (reported_rate && reported_rate != rate) + { + logf("uac_sa rate mismatch: ioctl %d, reported %d, expected %d", + status_ok, reported_rate, rate); + } +#endif + mixer_set_frequency(rate); + + dac_head = dac_tail = 0; + dac_running = true; + if (pthread_create(&dac_thread, NULL, dac_pump_thread, NULL) != 0) + { + logf("uac_sa pump thread failed"); + dac_running = false; + close(dac_fd); + dac_fd = -1; + return false; + } + + mixer_channel_set_amplitude(PCM_MIXER_CHAN_USBAUDIO, MIX_AMP_UNITY); + mixer_channel_play_data(PCM_MIXER_CHAN_USBAUDIO, &cbs, NULL, 0); + return true; +} + +void usb_dac_stop(void) +{ + if (!dac_running) + return; + + dac_running = false; + pthread_join(dac_thread, NULL); + mixer_channel_stop(PCM_MIXER_CHAN_USBAUDIO); + + if (dac_fd >= 0) + { + close(dac_fd); + dac_fd = -1; + } +} + +bool usb_audio_get_active(void) +{ + return dac_running; +} diff --git a/firmware/target/hosted/hiby/usb-dac-hiby.h b/firmware/target/hosted/hiby/usb-dac-hiby.h new file mode 100644 index 0000000000..2d6f15007a --- /dev/null +++ b/firmware/target/hosted/hiby/usb-dac-hiby.h @@ -0,0 +1,36 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2026 by Michael McAllister + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#ifndef __USB_DAC_HIBY_H__ +#define __USB_DAC_HIBY_H__ + +#define USB_DAC_SAMPLE_RATE SAMPR_48 + +/** + * Host-PCM pump for the HiBy USB DAC gadget mode: drains /dev/uac_sa + * into the PCM mixer. Started/stopped by the USB gadget driver when the + * uac_sa function is brought up/torn down. + * + * usb_dac_start() returns false if the uac_sa device never appears (the + * pump is not started); the caller should fall back to a safe USB mode. + */ +bool usb_dac_start(void); +void usb_dac_stop(void); + +#endif /* __USB_DAC_HIBY_H__ */ diff --git a/firmware/target/hosted/hiby/usb-hiby-gadget.c b/firmware/target/hosted/hiby/usb-hiby-gadget.c index 651b25bb70..c252f6d64e 100644 --- a/firmware/target/hosted/hiby/usb-hiby-gadget.c +++ b/firmware/target/hosted/hiby/usb-hiby-gadget.c @@ -8,6 +8,7 @@ * * Copyright (C) 2018 by Marcin Bukat * Copyright (C) 2025 by Melissa Autumn + * Copyright (C) 2026 by Michael McAllister * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -20,6 +21,7 @@ ****************************************************************************/ #include +#include #include #include #include @@ -32,6 +34,12 @@ #include "power.h" #include "usb-hiby-gadget.h" +#include "audio.h" +#include "pcm.h" +#include "pcm_sampr.h" +#include "usb_ch9.h" +#include "usb-dac-hiby.h" + #ifdef HAVE_GENERAL_PURPOSE_LED #include "led-general-purpose.h" #endif @@ -40,14 +48,18 @@ #include "logf.h" #include +/* Configfs root of the gadget created by usb_init_device() */ +#define USB_GADGET_PATH "/sys/kernel/config/usb_gadget/adb_demo" + static int _usb_mode = -1; static bool _usb_init = false; static bool is_adb_running = false; +static int _usb_audio = 0; /* usb_audio setting: 0 never .. 3 while mass-storage */ void enable_adb(void); void enable_mass_storage(void); void enable_charging(void); -void enable_usb_audio(void); +bool enable_usb_audio(void); void disable_adb(void); void disable_mass_storage(void); @@ -58,13 +70,9 @@ void cleanup_rbhome(void); void startup_rbhome(void); #endif -void hiby_set_usb_mode(int mode) { - logf(">>>>>>>>>>>>>>>>> hiby_set_usb_mode(%d)\n", mode); - if (!_usb_init) { - logf("Need to init usb!\n"); - usb_init_device(); - } - +/* Bring up the gadget for a base USB mode (mass storage / charge / adb). */ +static void configure_usb_mode(int mode) +{ switch(mode) { case USB_MODE_MASS_STORAGE: logf("Enabling Mass Storage\n"); @@ -74,11 +82,111 @@ void hiby_set_usb_mode(int mode) { logf("Enabling Charge\n"); enable_charging(); break; + case USB_MODE_ADB: + logf("Enabling ADB\n"); + enable_adb(); + break; default: break; } +} +/* Whether the USB-DAC function should be running, given the usb_audio + * setting (never/always/while_charge_only/while_mass_storage) and the + * current base USB mode. */ +static bool dac_should_run(void) +{ + switch (_usb_audio) { + case 1: return true; /* always */ + case 2: return _usb_mode == USB_MODE_CHARGE; /* while charge-only */ + case 3: return _usb_mode == USB_MODE_MASS_STORAGE; /* while mass-storage */ + default: return false; /* never */ + } +} + +/* Reconcile the DAC gadget with the setting + current mode. + * + * In adb mode the DAC rides alongside adb as a composite function, so we + * rebuild the adb gadget (enable_adb() composes uac_sa when dac_should_run()). + * In charge/mass-storage mode the DAC is a standalone self-bound gadget. */ +static void apply_usb_audio(void) +{ + if (_usb_mode == USB_MODE_ADB) { + enable_adb(); + return; + } + + bool want = dac_should_run(); + bool active = usb_audio_get_active(); + + if (want && !active) { + /* enable_usb_audio() returns false if applied before audio_init() + * (PCM mixer not up yet -- e.g. at boot) or if uac_sa never comes + * up. Either way restore the base mode so we never leave a dead + * gadget behind. */ + if (!enable_usb_audio()) + configure_usb_mode(_usb_mode); + } else if (!want && active) { + disable_usb_audio(); + configure_usb_mode(_usb_mode); + } +} + +/* usb_set_audio() is the target's implementation of the usb_audio setting + * callback declared in usb.h (HAVE_HOST_USB_AUDIO); it drives the DAC gadget + * up or down. */ +void usb_set_audio(int value) +{ + _usb_audio = value; + apply_usb_audio(); +} + +/* Compose uac_sa into the adb config, linked FIRST so the audio interfaces + * are 0-1 (the vendor uac_sa function hard-codes them), with the DAC VID/PID + * + IAD descriptor. Returns true if it composed the DAC. Skipped before + * audio_init() (no PCM mixer yet); re-applied once it is up. */ +static bool adb_compose_dac(void) +{ + if (!dac_should_run() || !pcm_is_initialized()) + return false; + + sysfs_set_string(USB_GADGET_PATH "/idVendor", "0x" USB_VID_STR); + sysfs_set_string(USB_GADGET_PATH "/idProduct", "0x0004"); + sysfs_set_int(USB_GADGET_PATH "/bDeviceClass", USB_CLASS_MISC); + sysfs_set_int(USB_GADGET_PATH "/bDeviceSubClass", 2); + sysfs_set_int(USB_GADGET_PATH "/bDeviceProtocol", 1); + + system("mkdir -p " USB_GADGET_PATH "/functions/uac_sa.a"); + sysfs_set_int(USB_GADGET_PATH "/functions/uac_sa.a/c_chmask", 3); + sysfs_set_int(USB_GADGET_PATH "/functions/uac_sa.a/c_ssize", 2); + sysfs_set_int(USB_GADGET_PATH "/functions/uac_sa.a/c_srate", USB_DAC_SAMPLE_RATE); + system("ln -sf " USB_GADGET_PATH "/functions/uac_sa.a " USB_GADGET_PATH "/configs/c.1/"); + sysfs_set_string(USB_GADGET_PATH "/configs/c.1/strings/0x409/configuration", "uac,adb"); + return true; +} + +void hiby_set_usb_mode(int mode) { + logf(">>>>>>>>>>>>>>>>> hiby_set_usb_mode(%d)\n", mode); + if (!_usb_init) { + logf("Need to init usb!\n"); + usb_init_device(); + } + + if (_usb_mode == mode) + return; + + /* Drop any active DAC before reconfiguring the base gadget. */ + if (usb_audio_get_active()) + disable_usb_audio(); + + /* Set the mode first so enable_adb()/dac_should_run() see it. */ _usb_mode = mode; + configure_usb_mode(mode); + + /* adb composes the DAC itself in enable_adb(); charge/mass-storage get + * the standalone self-bound DAC here. */ + if (mode != USB_MODE_ADB) + apply_usb_audio(); } /* TODO: implement usb detection properly */ @@ -90,11 +198,31 @@ int usb_detect(void) return power_input_status() == POWER_INPUT_USB_CHARGER ? USB_INSERTED : USB_EXTRACTED; } +static void set_mass_storage_lun(void) +{ + const char *device = "/dev/mmcblk0p1"; + + // If partition 1 doesn't exist we'll try the main device + if (access(device, F_OK) != 0) + device = "/dev/mmcblk0"; + + sysfs_set_string("/sys/kernel/config/usb_gadget/adb_demo/functions/mass_storage.0/lun.0/file", device); +} + void usb_enable(bool on) { logf(">>>>>>>>>>>>>>>>> usb_enable(%d)\n", on); logf("usb enable %d %d\n", on, _usb_mode); + /* ADB binds the UDC itself, and so does the USB-DAC while it is active, + * so leave the UDC alone in those cases. */ + if (_usb_mode == USB_MODE_ADB || usb_audio_get_active()) + return; + + // Re-arm the LUN on each connect, otherwise the disk is only exported the first time. + if (on && _usb_mode == USB_MODE_MASS_STORAGE) + set_mass_storage_lun(); + sysfs_set_string("/sys/kernel/config/usb_gadget/adb_demo/UDC", on ? "13500000.otg_new" : "\n"); } @@ -139,53 +267,67 @@ void enable_charging(void) { disable_mass_storage(); disable_adb(); + disable_usb_audio(); } -#if 1 void enable_adb(void) { logf(">>>>>>>>>>>>>>>>> set_adb()\n"); - // Disable mass storage if it was running + // Stop the respawner FIRST so it can't rebind adbd under us. + system("killall adbserver.sh adbd 2>/dev/null"); + + /* Disable mass storage and USB DAC if they were running (unbinds the UDC + * and clears any uac_sa link/descriptor). */ disable_mass_storage(); + disable_usb_audio(); - // Remove any lingering adb daemon - system("killall -9 adbd"); + /* Also drop the adb function link so we control the link order below + * (uac_sa must be linked before adb -- see adb_compose_dac()). */ + if (access(USB_GADGET_PATH "/configs/c.1/ffs.adb", F_OK) == 0) + system("rm " USB_GADGET_PATH "/configs/c.1/ffs.adb"); - system("mkdir -p /sys/kernel/config/usb_gadget/adb_demo/configs/c.1/strings/0x409"); - system("mkdir -p /sys/kernel/config/usb_gadget/adb_demo/functions/ffs.adb"); + system("mkdir -p " USB_GADGET_PATH "/configs/c.1/strings/0x409"); + system("mkdir -p " USB_GADGET_PATH "/functions/ffs.adb"); - // Now we'll override configuration and MaxPower - sysfs_set_string("/sys/kernel/config/usb_gadget/adb_demo/configs/c.1/strings/0x409/configuration", "adb"); - sysfs_set_int("/sys/kernel/config/usb_gadget/adb_demo/configs/c.1/MaxPower", 120); - - // And link up the adb function to the usb gadget config - system("ln -s /sys/kernel/config/usb_gadget/adb_demo/functions/ffs.adb /sys/kernel/config/usb_gadget/adb_demo/configs/c.1/"); - - int is_mounted = !system("mountpoint -q /dev/usb-ffs/adb"); - - if (!is_mounted) { - system("mkdir -p /dev/usb-ffs/adb"); - /* This seems to fail, but adb will still work and then it will be mounted. Not sure what's up here... */ - system("mount -t functionfs adb /dev/usb-ffs/adb"); + /* Compose the USB-DAC (uac_sa, linked first) alongside adb when the + * usb_audio setting wants it; otherwise present plain adb. */ + bool with_dac = adb_compose_dac(); + if (!with_dac) { + // Use the adb VID/PID so the host recognises the device + sysfs_set_string(USB_GADGET_PATH "/idVendor", "0x18d1"); + sysfs_set_string(USB_GADGET_PATH "/idProduct", "0xd002"); + sysfs_set_string(USB_GADGET_PATH "/configs/c.1/strings/0x409/configuration", "adb"); } - // Boot adb daemon - system("/usr/bin/adbd &"); + sysfs_set_int(USB_GADGET_PATH "/configs/c.1/MaxPower", 120); + + // Link adb into the config (after uac_sa, so adb is the later interface) + system("ln -sf " USB_GADGET_PATH "/functions/ffs.adb " USB_GADGET_PATH "/configs/c.1/"); + + system("mkdir -p /dev/usb-ffs/adb"); + + // Let the vendor respawner mount functionfs and run adbd, which binds the UDC. + system("/sbin/adbserver.sh 440 &"); + + /* uac_sa is bound by the same auto-bind; start pumping /dev/uac_sa. */ + if (with_dac) + usb_dac_start(); } void disable_adb(void) { - // Remove any lingering adb daemon - system("killall -9 adbd"); + // Remove any lingering adb daemon and its respawner + system("killall adbserver.sh adbd 2>/dev/null"); - // Remove the adb link to config - if (access("/sys/kernel/config/usb_gadget/adb_demo/configs/c.1/ffs.adb", F_OK) == 0) { - system("rm /sys/kernel/config/usb_gadget/adb_demo/configs/c.1/ffs.adb"); + // Unbind the UDC so the gadget can be reconfigured + if (access("/sys/kernel/config/usb_gadget/adb_demo/UDC", F_OK) == 0) { + sysfs_set_string("/sys/kernel/config/usb_gadget/adb_demo/UDC", "\n"); } - // Remove the adb function - if (access("/sys/kernel/config/usb_gadget/adb_demo/functions/ffs.adb", F_OK) == 0) { - system("rm -rf /sys/kernel/config/usb_gadget/adb_demo/functions/ffs.adb"); + // Unlink the adb function from config (the function is left in place and + // reused, so re-creating it in enable_adb can't block) + if (access("/sys/kernel/config/usb_gadget/adb_demo/configs/c.1/ffs.adb", F_OK) == 0) { + system("rm /sys/kernel/config/usb_gadget/adb_demo/configs/c.1/ffs.adb"); } // Reset the MaxPower to its default value @@ -200,19 +342,16 @@ void disable_adb(void) { // Unmount adb if (!system("mountpoint -q /dev/usb-ffs/adb")) { - system("unmount /dev/usb-ffs/adb"); + system("umount -l /dev/usb-ffs/adb"); } } -#else -void enable_adb(void) {} -void disable_adb(void) {} -#endif void enable_mass_storage(void) { logf(">>>>>>>>>>>>>>>>> set_mass_storage()\n"); - // Disable adb if it's running + /* Disable adb and USB DAC if they're running */ disable_adb(); + disable_usb_audio(); system("mkdir -p /sys/kernel/config/usb_gadget/adb_demo/functions/mass_storage.0/lun.0"); system("mkdir -p /sys/kernel/config/usb_gadget/adb_demo/configs/c.1/strings/0x409"); @@ -226,15 +365,7 @@ void enable_mass_storage(void) { system("ln -s /sys/kernel/config/usb_gadget/adb_demo/functions/mass_storage.0 /sys/kernel/config/usb_gadget/adb_demo/configs/c.1/"); - char mount_device[32] = "/dev/mmcblk0p1"; - - // If partition 1 doesn't exist we'll try the main device - if (access(mount_device, F_OK) != 0) { - memset(mount_device, 0, sizeof(mount_device)); - strcpy(mount_device, "/dev/mmcblk0"); - } - - sysfs_set_string("/sys/kernel/config/usb_gadget/adb_demo/functions/mass_storage.0/lun.0/file", mount_device); + set_mass_storage_lun(); } void disable_mass_storage(void) { @@ -259,18 +390,96 @@ void disable_mass_storage(void) { } } -#if defined(HAVE_USB_AUDIO) -void enable_usb_audio(void) { +/* USB DAC (USB Audio Class gadget). + * + * The vendor kernel provides a UAC function "uac_sa"; building it into the + * gadget and binding the UDC creates /dev/uac_sa, which usb-dac-hiby.c pumps + * into the PCM mixer. enable_usb_audio() is the standalone path used in + * charge/mass-storage mode; adb mode composes uac_sa alongside adb in + * enable_adb() instead. */ +bool enable_usb_audio(void) +{ + logf(">>>>>>>>>>>>>>>>> enable_usb_audio()\n"); + + /* The DAC pump feeds the PCM mixer, which isn't up until audio_init(), so + * a DAC setting applied at boot lands too early. Bail before touching + * anything so the caller can restore the base mode. */ + if (!pcm_is_initialized()) + return false; + + /* The DAC hands the output path to the host's PCM; stop local playback + * first (it can't restart while the DAC is active). */ + audio_stop(); + + /* Standalone DAC: tear down the other gadget functions first. */ + disable_mass_storage(); + disable_adb(); + + /* Build the "uac_sa" function: stereo, 16-bit, capture (host->device). + * Set all three explicitly: the kernel's defaults are 3/2/64000, so at + * least the 64 kHz capture rate must always be overridden. */ + system("mkdir -p " USB_GADGET_PATH "/functions/uac_sa.a"); + sysfs_set_int(USB_GADGET_PATH "/functions/uac_sa.a/c_chmask", 3); + sysfs_set_int(USB_GADGET_PATH "/functions/uac_sa.a/c_ssize", 2); + sysfs_set_int(USB_GADGET_PATH "/functions/uac_sa.a/c_srate", USB_DAC_SAMPLE_RATE); + + /* Present an IAD audio device with the HiBy DAC VID/PID so the host + * recognises it as a USB DAC. */ + sysfs_set_int(USB_GADGET_PATH "/bDeviceClass", USB_CLASS_MISC); + sysfs_set_int(USB_GADGET_PATH "/bDeviceSubClass", 2); + sysfs_set_int(USB_GADGET_PATH "/bDeviceProtocol", 1); + sysfs_set_string(USB_GADGET_PATH "/idVendor", "0x" USB_VID_STR); + sysfs_set_string(USB_GADGET_PATH "/idProduct", "0x0004"); + + system("mkdir -p " USB_GADGET_PATH "/configs/c.1/strings/0x409"); + sysfs_set_string(USB_GADGET_PATH + "/configs/c.1/strings/0x409/configuration", "uac"); + sysfs_set_int(USB_GADGET_PATH "/configs/c.1/bmAttributes", + USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER); + sysfs_set_int(USB_GADGET_PATH "/configs/c.1/MaxPower", 120); + + system("ln -sf " USB_GADGET_PATH "/functions/uac_sa.a " + USB_GADGET_PATH "/configs/c.1/"); + + /* Bind the UDC ourselves -- in charge mode the usb thread is power-only, + * and usb_enable() bails while the DAC is active, so nothing else binds + * it. This creates /dev/uac_sa. */ + sysfs_set_string(USB_GADGET_PATH "/UDC", "13500000.otg_new"); + + return usb_dac_start(); } -void disable_usb_audio(void) { +void disable_usb_audio(void) +{ + logf(">>>>>>>>>>>>>>>>> disable_usb_audio()\n"); + usb_dac_stop(); + + /* Unbind the UDC and tear the function down so the next mode starts + * from a clean gadget. */ + if (access(USB_GADGET_PATH "/UDC", F_OK) == 0) + sysfs_set_string(USB_GADGET_PATH "/UDC", "\n"); + + if (access(USB_GADGET_PATH "/configs/c.1/uac_sa.a", F_OK) == 0) + system("rm " USB_GADGET_PATH "/configs/c.1/uac_sa.a"); + + if (access(USB_GADGET_PATH "/functions/uac_sa.a", F_OK) == 0) + system("rmdir " USB_GADGET_PATH "/functions/uac_sa.a"); + + /* Restore a plain (non-IAD) device descriptor and the target's own + * VID/PID for the other modes. */ + sysfs_set_int(USB_GADGET_PATH "/bDeviceClass", USB_CLASS_PER_INTERFACE); + sysfs_set_int(USB_GADGET_PATH "/bDeviceSubClass", 0); + sysfs_set_int(USB_GADGET_PATH "/bDeviceProtocol", 0); + sysfs_set_string(USB_GADGET_PATH "/idVendor", "0x" USB_VID_STR); + sysfs_set_string(USB_GADGET_PATH "/idProduct", "0x" USB_PID_STR); + + if (access(USB_GADGET_PATH + "/configs/c.1/strings/0x409/configuration", F_OK) == 0) + sysfs_set_string(USB_GADGET_PATH + "/configs/c.1/strings/0x409/configuration", ""); } -#else -void enable_usb_audio(void) {} -void disable_usb_audio(void) {} -#endif void usb_init_device(void) { diff --git a/firmware/target/hosted/ibasso/pcm-ibasso.c b/firmware/target/hosted/ibasso/pcm-ibasso.c index ea5651ac7e..94f9359029 100644 --- a/firmware/target/hosted/ibasso/pcm-ibasso.c +++ b/firmware/target/hosted/ibasso/pcm-ibasso.c @@ -33,6 +33,7 @@ #include "pcm-internal.h" #include "pcm_sampr.h" #include "pcm_sink.h" +#include "audiohw.h" #include "sound/asound.h" #include "tinyalsa/asoundlib.h" @@ -458,6 +459,7 @@ struct pcm_sink builtin_pcm_sink = { .samprs = hw_freq_sampr, .num_samprs = HW_NUM_FREQ, .default_freq = HW_FREQ_DEFAULT, + .volume_type = PCM_NATIVE_VOLUME_TYPE, }, .ops = { .init = sink_dma_init, diff --git a/firmware/target/hosted/lcd-linuxfb.c b/firmware/target/hosted/lcd-linuxfb.c index e25dd37b21..63661a1254 100644 --- a/firmware/target/hosted/lcd-linuxfb.c +++ b/firmware/target/hosted/lcd-linuxfb.c @@ -106,6 +106,10 @@ void lcd_init_device(void) vinfo.yres, vinfo.bits_per_pixel); } #endif +#elif defined(FB_STRIDE_MISMATCH) + /* For framebuffers wider than the panel and cannot be resized */ + if (vinfo.bits_per_pixel != LCD_DEPTH) + panicf("Unexpected framebuffer depth: %d", vinfo.bits_per_pixel); #else /* Make sure we match our desired bitdepth */ if (vinfo.bits_per_pixel != LCD_DEPTH || vinfo.xres != LCD_WIDTH || vinfo.yres != LCD_HEIGHT) { @@ -167,6 +171,20 @@ void lcd_enable(bool on) extern void lcd_copy_buffer_rect(fb_data *dst, const fb_data *src, int width, int height); +#ifdef FB_STRIDE_MISMATCH +/* lcd_copy_buffer_rect() for framebuffers wider than the panel */ +static void fb_copy_rect(fb_data *dst, const fb_data *src, + int width, int height) +{ + while (height--) + { + memcpy(dst, src, width * sizeof(fb_data)); + dst += FB_STRIDE_MISMATCH; + src += LCD_WIDTH; + } +} +#endif + void lcd_update(void) { if (fd < 0) return; @@ -176,9 +194,15 @@ void lcd_update(void) #endif { /* Copy the Rockbox framebuffer to the second framebuffer */ +#ifdef FB_STRIDE_MISMATCH + /* Strides differ - not one contiguous run */ + fb_copy_rect(LCD_FRAMEBUF_ADDR(0, 0), FBADDR(0, 0), + LCD_WIDTH, LCD_HEIGHT); +#else fb_data *dst = LCD_FRAMEBUF_ADDR(0, 0) + (fb_plane * FRAMEBUFFER_SIZE); lcd_copy_buffer_rect(dst, FBADDR(0,0), LCD_WIDTH*LCD_HEIGHT, 1); +#endif redraw(); } } @@ -191,6 +215,10 @@ void lcd_update_rect(int x, int y, int width, int height) if (lcd_active()) #endif { +#ifdef FB_STRIDE_MISMATCH + /* Strides differ - do line-by-line */ + fb_copy_rect(LCD_FRAMEBUF_ADDR(x, y), FBADDR(x, y), width, height); +#else fb_data *dst = LCD_FRAMEBUF_ADDR(x, y) + (fb_plane * FRAMEBUFFER_SIZE); fb_data *src = FBADDR(x,y); @@ -205,6 +233,7 @@ void lcd_update_rect(int x, int y, int width, int height) /* Full width - copy as one line */ lcd_copy_buffer_rect(dst, src, LCD_WIDTH*height, 1); } +#endif redraw(); } } diff --git a/firmware/target/hosted/pcm-alsa.c b/firmware/target/hosted/pcm-alsa.c index 7e4833fdfe..10b379b03d 100644 --- a/firmware/target/hosted/pcm-alsa.c +++ b/firmware/target/hosted/pcm-alsa.c @@ -775,6 +775,7 @@ struct pcm_sink builtin_pcm_sink = { .samprs = hw_freq_sampr, .num_samprs = HW_NUM_FREQ, .default_freq = HW_FREQ_DEFAULT, + .volume_type = PCM_NATIVE_VOLUME_TYPE, }, .ops = { .init = sink_dma_init, diff --git a/firmware/target/hosted/rtc.c b/firmware/target/hosted/rtc.c index 959b1cb8c1..9cb00b6422 100644 --- a/firmware/target/hosted/rtc.c +++ b/firmware/target/hosted/rtc.c @@ -23,7 +23,7 @@ #include #if !defined(WIN32) #include -#if !defined(__APPLE__) && !defined(CTRU) +#if !defined(__APPLE__) && !defined(N3DS) #include #endif #include @@ -48,7 +48,7 @@ int rtc_read_datetime(struct tm *tm) int rtc_write_datetime(const struct tm *tm) { -#if !defined(WIN32) && !defined(__APPLE__) && !defined(CTRU) +#if !defined(WIN32) && !defined(__APPLE__) && !defined(N3DS) struct timeval tv; struct tm *tm_time; diff --git a/firmware/target/hosted/sdl/pcm-sdl.c b/firmware/target/hosted/sdl/pcm-sdl.c index 2b9df50faf..d2ed462a4d 100644 --- a/firmware/target/hosted/sdl/pcm-sdl.c +++ b/firmware/target/hosted/sdl/pcm-sdl.c @@ -449,6 +449,7 @@ struct pcm_sink builtin_pcm_sink = { .samprs = hw_freq_sampr, .num_samprs = HW_NUM_FREQ, .default_freq = HW_FREQ_DEFAULT, + .volume_type = PCM_NATIVE_VOLUME_TYPE, }, .ops = { .init = sink_dma_init, diff --git a/firmware/target/hosted/sdl/sim-ui-defines.h b/firmware/target/hosted/sdl/sim-ui-defines.h index 3a10589da6..f6686ca619 100644 --- a/firmware/target/hosted/sdl/sim-ui-defines.h +++ b/firmware/target/hosted/sdl/sim-ui-defines.h @@ -390,6 +390,13 @@ #define UI_WIDTH LCD_WIDTH #define UI_HEIGHT LCD_HEIGHT +#elif defined(SAMSUNG_YPCP3) +#define UI_TITLE "Samsung YP-CP3" +#define UI_WIDTH 614 /* width of GUI window */ +#define UI_HEIGHT 324 /* height of GUI window */ +#define UI_LCD_POSX 40 +#define UI_LCD_POSY 37 + #elif defined(SAMSUNG_YPR0) #define UI_TITLE "Samsung YP-R0" #define UI_WIDTH 313 /* width of GUI window */ @@ -525,6 +532,13 @@ #define UI_LCD_POSX 77 #define UI_LCD_POSY 74 +#elif defined(HIDIZS_AP80MAX) +#define UI_TITLE "Hidizs AP80 Pro Max" +#define UI_WIDTH 580 +#define UI_HEIGHT 880 +#define UI_LCD_POSX 100 +#define UI_LCD_POSY 100 + #elif defined(SIMULATOR) #error no UI defines #endif diff --git a/firmware/target/hosted/sysfs.c b/firmware/target/hosted/sysfs.c index 1c3fe8c396..7bacb3de54 100644 --- a/firmware/target/hosted/sysfs.c +++ b/firmware/target/hosted/sysfs.c @@ -178,7 +178,7 @@ bool sysfs_get_string(const char *path, char *value, int size) } -bool sysfs_set_string(const char *path, char *value) +bool sysfs_set_string(const char *path, const char *value) { FILE *f = open_write(path); if(f == NULL) diff --git a/firmware/target/hosted/sysfs.h b/firmware/target/hosted/sysfs.h index 639cc1c409..de2b8db31e 100644 --- a/firmware/target/hosted/sysfs.h +++ b/firmware/target/hosted/sysfs.h @@ -28,4 +28,4 @@ bool sysfs_set_int(const char *path, int value); bool sysfs_get_char(const char *path, char *value); bool sysfs_set_char(const char *path, char value); bool sysfs_get_string(const char *path, char *value, int size); -bool sysfs_set_string(const char *path, char *value); +bool sysfs_set_string(const char *path, const char *value); diff --git a/firmware/target/mips/ingenic_x1000/dma-x1000.c b/firmware/target/mips/ingenic/dma-ingenic.c similarity index 99% rename from firmware/target/mips/ingenic_x1000/dma-x1000.c rename to firmware/target/mips/ingenic/dma-ingenic.c index f3b68494fb..8a6a0e3361 100644 --- a/firmware/target/mips/ingenic_x1000/dma-x1000.c +++ b/firmware/target/mips/ingenic/dma-ingenic.c @@ -19,7 +19,7 @@ ****************************************************************************/ #include "system.h" -#include "dma-x1000.h" +#include "dma-ingenic.h" #include "irq-x1000.h" #include "x1000/cpm.h" #include "panic.h" diff --git a/firmware/target/mips/ingenic_x1000/dma-x1000.h b/firmware/target/mips/ingenic/dma-ingenic.h similarity index 96% rename from firmware/target/mips/ingenic_x1000/dma-x1000.h rename to firmware/target/mips/ingenic/dma-ingenic.h index 514d6e3097..15d7fda120 100644 --- a/firmware/target/mips/ingenic_x1000/dma-x1000.h +++ b/firmware/target/mips/ingenic/dma-ingenic.h @@ -18,8 +18,8 @@ * ****************************************************************************/ -#ifndef __DMA_X1000_H__ -#define __DMA_X1000_H__ +#ifndef __DMA_INGENIC_H__ +#define __DMA_INGENIC_H__ #include "x1000/dma.h" #include "x1000/dma_chn.h" @@ -66,4 +66,4 @@ typedef void(*dma_cb_func)(int event); extern void dma_init(void) INIT_ATTR; extern void dma_set_callback(int chn, dma_cb_func cb); -#endif /* __DMA_X1000_H__ */ +#endif /* __DMA_INGENIC_H__ */ diff --git a/firmware/target/mips/ingenic_x1000/gpio-x1000.c b/firmware/target/mips/ingenic/gpio-ingenic.c similarity index 99% rename from firmware/target/mips/ingenic_x1000/gpio-x1000.c rename to firmware/target/mips/ingenic/gpio-ingenic.c index b67eb1739c..61a46b7cb9 100644 --- a/firmware/target/mips/ingenic_x1000/gpio-x1000.c +++ b/firmware/target/mips/ingenic/gpio-ingenic.c @@ -18,7 +18,7 @@ * ****************************************************************************/ -#include "gpio-x1000.h" +#include "gpio-ingenic.h" #if defined(EROS_QN) # include "devicedata.h" #endif diff --git a/firmware/target/mips/ingenic_x1000/gpio-x1000.h b/firmware/target/mips/ingenic/gpio-ingenic.h similarity index 98% rename from firmware/target/mips/ingenic_x1000/gpio-x1000.h rename to firmware/target/mips/ingenic/gpio-ingenic.h index 3798027cf8..197c1f86f3 100644 --- a/firmware/target/mips/ingenic_x1000/gpio-x1000.h +++ b/firmware/target/mips/ingenic/gpio-ingenic.h @@ -18,8 +18,8 @@ * ****************************************************************************/ -#ifndef __GPIO_X1000_H__ -#define __GPIO_X1000_H__ +#ifndef __GPIO_INGENIC_H__ +#define __GPIO_INGENIC_H__ #include "x1000/gpio.h" #include "config.h" @@ -167,4 +167,4 @@ static inline void gpio_flip_edge_irq(int gpio) gpio_set_irq_level(gpio, 1); } -#endif /* __GPIO_X1000_H__ */ +#endif /* __GPIO_INGENIC_H__ */ diff --git a/firmware/target/mips/ingenic_x1000/i2c-x1000.c b/firmware/target/mips/ingenic/i2c-ingenic.c similarity index 99% rename from firmware/target/mips/ingenic_x1000/i2c-x1000.c rename to firmware/target/mips/ingenic/i2c-ingenic.c index c73f78fb2a..0abf763aa7 100644 --- a/firmware/target/mips/ingenic_x1000/i2c-x1000.c +++ b/firmware/target/mips/ingenic/i2c-ingenic.c @@ -19,7 +19,7 @@ ****************************************************************************/ /* #define LOGF_ENABLE */ -#include "i2c-x1000.h" +#include "i2c-ingenic.h" #include "system.h" #include "kernel.h" #include "panic.h" diff --git a/firmware/target/mips/ingenic_x1000/i2c-x1000.h b/firmware/target/mips/ingenic/i2c-ingenic.h similarity index 94% rename from firmware/target/mips/ingenic_x1000/i2c-x1000.h rename to firmware/target/mips/ingenic/i2c-ingenic.h index 12ac5465d4..f0d5077c39 100644 --- a/firmware/target/mips/ingenic_x1000/i2c-x1000.h +++ b/firmware/target/mips/ingenic/i2c-ingenic.h @@ -18,8 +18,8 @@ * ****************************************************************************/ -#ifndef __I2C_X1000_H__ -#define __I2C_X1000_H__ +#ifndef __I2C_INGENIC_H__ +#define __I2C_INGENIC_H__ #include "i2c-async.h" @@ -40,4 +40,4 @@ extern void i2c_init(void) INIT_ATTR; */ extern void i2c_x1000_set_freq(int chn, int freq); -#endif /* __I2C_X1000_H__ */ +#endif /* __I2C_INGENIC_H__ */ diff --git a/firmware/target/mips/ingenic_x1000/installer-x1000.c b/firmware/target/mips/ingenic/installer-ingenic.c similarity index 98% rename from firmware/target/mips/ingenic_x1000/installer-x1000.c rename to firmware/target/mips/ingenic/installer-ingenic.c index 769371d29d..2aba947758 100644 --- a/firmware/target/mips/ingenic_x1000/installer-x1000.c +++ b/firmware/target/mips/ingenic/installer-ingenic.c @@ -18,8 +18,8 @@ * ****************************************************************************/ -#include "installer-x1000.h" -#include "nand-x1000.h" +#include "installer-ingenic.h" +#include "nand-ingenic.h" #include "core_alloc.h" #include "file.h" #include "microtar-rockbox.h" @@ -142,6 +142,8 @@ static int updater_init(struct updater* u) nand_lock(u->ndrv); rc = nand_open(u->ndrv); if(rc != NAND_SUCCESS) { + nand_unlock(u->ndrv); + u->ndrv = NULL; rc = IERR_NAND_OPEN; goto error; } @@ -237,7 +239,7 @@ int install_bootloader(const char* filename) int backup_bootloader(const char* filename) { - int rc, fd = 0; + int rc, fd = -1; struct updater u; rc = updater_init(&u); @@ -281,7 +283,7 @@ int backup_bootloader(const char* filename) int restore_bootloader(const char* filename) { - int rc, fd = 0; + int rc, fd = -1; struct updater u; rc = updater_init(&u); diff --git a/firmware/target/mips/ingenic_x1000/installer-x1000.h b/firmware/target/mips/ingenic/installer-ingenic.h similarity index 95% rename from firmware/target/mips/ingenic_x1000/installer-x1000.h rename to firmware/target/mips/ingenic/installer-ingenic.h index 47bc7d7bfb..416db57906 100644 --- a/firmware/target/mips/ingenic_x1000/installer-x1000.h +++ b/firmware/target/mips/ingenic/installer-ingenic.h @@ -18,8 +18,8 @@ * ****************************************************************************/ -#ifndef __INSTALLER_X1000_H__ -#define __INSTALLER_X1000_H__ +#ifndef __INSTALLER_INGENIC_H__ +#define __INSTALLER_INGENIC_H__ /* This API is for the bootloader recovery menu and Rockbox utility to handle * bootloader installation, backup, and restore. @@ -52,4 +52,4 @@ extern int backup_bootloader(const char* filename); extern int restore_bootloader(const char* filename); extern const char* installer_strerror(int rc); -#endif /* __INSTALLER_X1000_H__ */ +#endif /* __INSTALLER_INGENIC_H__ */ diff --git a/firmware/target/mips/ingenic_x1000/kernel-x1000.c b/firmware/target/mips/ingenic/kernel-ingenic.c similarity index 100% rename from firmware/target/mips/ingenic_x1000/kernel-x1000.c rename to firmware/target/mips/ingenic/kernel-ingenic.c diff --git a/firmware/target/mips/ingenic_x1000/nand-x1000.c b/firmware/target/mips/ingenic/nand-ingenic.c similarity index 99% rename from firmware/target/mips/ingenic_x1000/nand-x1000.c rename to firmware/target/mips/ingenic/nand-ingenic.c index 39e06b1964..67f0f17f4d 100644 --- a/firmware/target/mips/ingenic_x1000/nand-x1000.c +++ b/firmware/target/mips/ingenic/nand-ingenic.c @@ -18,7 +18,7 @@ * ****************************************************************************/ -#include "nand-x1000.h" +#include "nand-ingenic.h" #include "sfc-x1000.h" #include "system.h" #include "logf.h" @@ -143,6 +143,7 @@ struct nand_drv* nand_init(void) static_nand_drv.scratch_buf = static_scratch_buf; static_nand_drv.page_buf = static_page_buf; static_nand_drv.refcount = 0; + inited = true; } return &static_nand_drv; diff --git a/firmware/target/mips/ingenic_x1000/nand-x1000.h b/firmware/target/mips/ingenic/nand-ingenic.h similarity index 99% rename from firmware/target/mips/ingenic_x1000/nand-x1000.h rename to firmware/target/mips/ingenic/nand-ingenic.h index c0796528c7..b330ea8a92 100644 --- a/firmware/target/mips/ingenic_x1000/nand-x1000.h +++ b/firmware/target/mips/ingenic/nand-ingenic.h @@ -18,8 +18,8 @@ * ****************************************************************************/ -#ifndef __NAND_X1000_H__ -#define __NAND_X1000_H__ +#ifndef __NAND_INGENIC_H__ +#define __NAND_INGENIC_H__ #include #include @@ -238,4 +238,4 @@ extern int nand_page_read(struct nand_drv* drv, nand_page_t page, void* buffer); extern int nand_read_bytes(struct nand_drv* drv, uint32_t byte_addr, uint32_t byte_len, void* buffer); extern int nand_write_bytes(struct nand_drv* drv, uint32_t byte_addr, uint32_t byte_len, const void* buffer); -#endif /* __NAND_X1000_H__ */ +#endif /* __NAND_INGENIC_H__ */ diff --git a/firmware/target/mips/ingenic_x1000/spl-nand-x1000.c b/firmware/target/mips/ingenic/spl-nand-ingenic.c similarity index 96% rename from firmware/target/mips/ingenic_x1000/spl-nand-x1000.c rename to firmware/target/mips/ingenic/spl-nand-ingenic.c index b9e5b53336..9af5edaece 100644 --- a/firmware/target/mips/ingenic_x1000/spl-nand-x1000.c +++ b/firmware/target/mips/ingenic/spl-nand-ingenic.c @@ -19,8 +19,8 @@ ****************************************************************************/ #include "spl-x1000.h" -#include "gpio-x1000.h" -#include "nand-x1000.h" +#include "gpio-ingenic.h" +#include "nand-ingenic.h" static struct nand_drv* ndrv = NULL; diff --git a/firmware/target/mips/ingenic_jz47xx/pcm-jz4740.c b/firmware/target/mips/ingenic_jz47xx/pcm-jz4740.c index a843480309..93fc4f4ca6 100644 --- a/firmware/target/mips/ingenic_jz47xx/pcm-jz4740.c +++ b/firmware/target/mips/ingenic_jz47xx/pcm-jz4740.c @@ -193,6 +193,7 @@ struct pcm_sink builtin_pcm_sink = { .samprs = hw_freq_sampr, .num_samprs = HW_NUM_FREQ, .default_freq = HW_FREQ_DEFAULT, + .volume_type = PCM_NATIVE_VOLUME_TYPE, }, .ops = { .init = sink_dma_init, diff --git a/firmware/target/mips/ingenic_jz47xx/pcm-jz4760.c b/firmware/target/mips/ingenic_jz47xx/pcm-jz4760.c index 37718d08da..29e35b63a4 100644 --- a/firmware/target/mips/ingenic_jz47xx/pcm-jz4760.c +++ b/firmware/target/mips/ingenic_jz47xx/pcm-jz4760.c @@ -178,6 +178,7 @@ struct pcm_sink builtin_pcm_sink = { .samprs = hw_freq_sampr, .num_samprs = HW_NUM_FREQ, .default_freq = HW_FREQ_DEFAULT, + .volume_type = PCM_NATIVE_VOLUME_TYPE, }, .ops = { .init = sink_dma_init, diff --git a/firmware/target/mips/ingenic_jz47xx/system-jz4740.c b/firmware/target/mips/ingenic_jz47xx/system-jz4740.c index bf0e46138b..a854f6bf8c 100644 --- a/firmware/target/mips/ingenic_jz47xx/system-jz4740.c +++ b/firmware/target/mips/ingenic_jz47xx/system-jz4740.c @@ -500,12 +500,6 @@ void system_init(void) { } -int system_memory_guard(int newmode) -{ - (void)newmode; - return 0; -} - #ifdef HAVE_ADJUSTABLE_CPU_FREQ void set_cpu_frequency(long frequency) { diff --git a/firmware/target/mips/ingenic_jz47xx/system-jz4760.c b/firmware/target/mips/ingenic_jz47xx/system-jz4760.c index 7d2a5c7109..aa2e8330fd 100644 --- a/firmware/target/mips/ingenic_jz47xx/system-jz4760.c +++ b/firmware/target/mips/ingenic_jz47xx/system-jz4760.c @@ -733,12 +733,6 @@ void system_init(void) { } -int system_memory_guard(int newmode) -{ - (void)newmode; - return 0; -} - #ifdef HAVE_ADJUSTABLE_CPU_FREQ void set_cpu_frequency(long frequency) { diff --git a/firmware/target/mips/ingenic_jz47xx/usb-jz4740.c b/firmware/target/mips/ingenic_jz47xx/usb-jz4740.c index 828dbaf2c4..e87e84fc4b 100644 --- a/firmware/target/mips/ingenic_jz47xx/usb-jz4740.c +++ b/firmware/target/mips/ingenic_jz47xx/usb-jz4740.c @@ -87,6 +87,17 @@ struct usb_drv_ep_spec usb_drv_ep_specs[USB_NUM_ENDPOINTS] = { }; uint8_t usb_drv_ep_specs_flags = 0; +static bool address_status; +static bool handle_set_address(struct usb_ctrlrequest *req); + +/* SET_ADDRESS status belongs to this driver, not the core EP0 machine. */ +static void complete_transfer(int ep, int dir, int status, int length) +{ + if(ep == EP_CONTROL && address_status) + return; + usb_core_transfer_complete(ep, dir, status, length); +} + static inline void select_endpoint(int ep) { REG_USB_REG_INDEX = ep; @@ -199,7 +210,7 @@ static void EP0_send(void) if(ep->sent >= ep->length) { REG_USB_REG_CSR0 = (csr0 | USB_CSR0_INPKTRDY | USB_CSR0_DATAEND); /* Set data end! */ - usb_core_transfer_complete(EP_CONTROL, USB_DIR_IN, 0, ep->sent); + complete_transfer(EP_CONTROL, USB_DIR_IN, 0, ep->sent); ep_transfer_completed(ep); } else @@ -245,7 +256,8 @@ static void EP0_handler(void) { readFIFO(ep_recv, REG_USB_REG_COUNT0); REG_USB_REG_CSR0 = csr0 | USB_CSR0_SVDOUTPKTRDY; /* clear OUTPKTRDY bit */ - usb_core_setup_received((struct usb_ctrlrequest*)ep_recv->buf); + if(!handle_set_address((struct usb_ctrlrequest*)ep_recv->buf)) + usb_core_setup_received((struct usb_ctrlrequest*)ep_recv->buf); } } @@ -292,7 +304,7 @@ static void EPIN_handler(unsigned int endpoint) if(ep->sent >= ep->length) { - usb_core_transfer_complete(endpoint, USB_DIR_IN, 0, ep->sent); + complete_transfer(endpoint, USB_DIR_IN, 0, ep->sent); ep_transfer_completed(ep); logf("sent complete"); } @@ -340,7 +352,7 @@ static void EPOUT_handler(unsigned int endpoint) if(size < ep->fifo_size || ep->received >= ep->length) { - usb_core_transfer_complete(endpoint, USB_DIR_OUT, 0, ep->received); + complete_transfer(endpoint, USB_DIR_OUT, 0, ep->received); ep_transfer_completed(ep); logf("receive transfer_complete"); } @@ -387,7 +399,7 @@ static void EPDMA_handler(int number) REG_USB_REG_INCSR |= USB_INCSR_INPKTRDY; } - usb_core_transfer_complete(endpoint, EP_IS_IN(ep) ? USB_DIR_IN : USB_DIR_OUT, + complete_transfer(endpoint, EP_IS_IN(ep) ? USB_DIR_IN : USB_DIR_OUT, 0, ep->length); ep_transfer_completed(ep); } @@ -486,7 +498,8 @@ static void udc_reset(void) REG_USB_REG_INTRINE |= USB_INTR_EP0; REG_USB_REG_INTRUSBE |= USB_INTR_RESET; - usb_core_bus_reset(); + address_status = false; + usb_core_bus_reset(); } /* Interrupt handler */ @@ -682,13 +695,6 @@ void usb_drv_exit(void) __cpm_stop_udc(); } -void usb_drv_set_address(int address) -{ - logf("%s(%d)", __func__, address); - - REG_USB_REG_FADDR = address; -} - static void usb_drv_send_internal(struct usb_endpoint* ep, void* ptr, int length, bool blocking) { if(ep->type == ep_control && ptr == NULL && length == 0) @@ -847,3 +853,18 @@ void usb_drv_ep_deinit(const struct usb_drv_ep_alloc_ctx* ctx, int ep) (void)ctx; (void)ep; } + +static bool handle_set_address(struct usb_ctrlrequest *req) +{ + address_status = false; + if(!usb_drv_is_set_address(req)) + return false; + + const uint8_t address = req->wValue & 0x7f; + address_status = true; + usb_drv_cancel_all_transfers(); + usb_drv_send_nonblocking(EP_CONTROL, NULL, 0); + REG_USB_REG_FADDR = address; + usb_core_notify_set_address(address); + return true; +} diff --git a/firmware/target/mips/ingenic_jz47xx/usb-jz4760.c b/firmware/target/mips/ingenic_jz47xx/usb-jz4760.c index 217b097a15..17669104c1 100644 --- a/firmware/target/mips/ingenic_jz47xx/usb-jz4760.c +++ b/firmware/target/mips/ingenic_jz47xx/usb-jz4760.c @@ -115,6 +115,17 @@ struct usb_drv_ep_spec usb_drv_ep_specs[USB_NUM_ENDPOINTS] = { }; uint8_t usb_drv_ep_specs_flags = 0; +static bool address_status; +static bool handle_set_address(struct usb_ctrlrequest *req); + +/* SET_ADDRESS status belongs to this driver, not the core EP0 machine. */ +static void complete_transfer(int ep, int dir, int status, int length) +{ + if(ep == EP_CONTROL && address_status) + return; + usb_core_transfer_complete(ep, dir, status, length); +} + static inline void select_endpoint(int ep) { REG_USB_INDEX = ep; @@ -236,7 +247,7 @@ static void EP0_send(void) if (ep->sent >= ep->length) { REG_USB_CSR0 = (csr0 | USB_CSR0_INPKTRDY | USB_CSR0_DATAEND); /* Set data end! */ if (!ep->wait) { - usb_core_transfer_complete(EP_CONTROL, USB_DIR_IN, 0, ep->sent); + complete_transfer(EP_CONTROL, USB_DIR_IN, 0, ep->sent); } ep->rc = 0; ep_transfer_completed(ep); @@ -277,11 +288,11 @@ static void EP0_handler(void) ep0_data_requested = false; if (ep_send->busy) { if (!ep_send->wait) - usb_core_transfer_complete(EP_CONTROL, USB_DIR_IN, -1, 0); + complete_transfer(EP_CONTROL, USB_DIR_IN, -1, 0); ep_transfer_completed(ep_send); } if (ep_recv->busy) { - usb_core_transfer_complete(EP_CONTROL, USB_DIR_OUT, -1, 0); + complete_transfer(EP_CONTROL, USB_DIR_OUT, -1, 0); ep_transfer_completed(ep_recv); } } @@ -290,7 +301,7 @@ static void EP0_handler(void) if (csr0 & USB_CSR0_OUTPKTRDY) { /* There is a packet in the fifo */ if (ep_send->busy) { if (!ep_send->wait) { - usb_core_transfer_complete(EP_CONTROL, USB_DIR_IN, -1, 0); + complete_transfer(EP_CONTROL, USB_DIR_IN, -1, 0); } ep_transfer_completed(ep_send); } @@ -300,7 +311,7 @@ static void EP0_handler(void) ep_recv->received += size; if (size < ep_recv->fifo_size || ep_recv->received >= ep_recv->length) { REG_USB_CSR0 = csr0 | USB_CSR0_SVDOUTPKTRDY | USB_CSR0_DATAEND; /* Set data end! */ - usb_core_transfer_complete(EP_CONTROL, USB_DIR_OUT, 0, ep_recv->received); + complete_transfer(EP_CONTROL, USB_DIR_OUT, 0, ep_recv->received); ep_transfer_completed(ep_recv); } else { REG_USB_CSR0 = csr0 | USB_CSR0_SVDOUTPKTRDY; /* clear OUTPKTRDY bit */ @@ -319,7 +330,8 @@ static void EP0_handler(void) ep0_data_supplied = true; } REG_USB_CSR0 = csr0; - usb_core_setup_received(&ep0_rx.request); + if(!handle_set_address(&ep0_rx.request)) + usb_core_setup_received(&ep0_rx.request); ep_transfer_completed(ep_recv); } } @@ -479,7 +491,7 @@ static void EPIN_complete(unsigned int endpoint) if (ep->sent >= ep->length) { if (!ep->wait) - usb_core_transfer_complete(endpoint, USB_DIR_IN, 0, ep->sent); + complete_transfer(endpoint, USB_DIR_IN, 0, ep->sent); ep->rc = 0; ep_transfer_completed(ep); logf("send complete"); @@ -577,7 +589,7 @@ static void EPOUT_handler(unsigned int endpoint) logf("received: %d max length: %d", ep->received, ep->length); if (size < ep->fifo_size || ep->received >= ep->length) { - usb_core_transfer_complete(endpoint, USB_DIR_OUT, 0, ep->received); + complete_transfer(endpoint, USB_DIR_OUT, 0, ep->received); ep_transfer_completed(ep); logf("receive transfer_complete"); } @@ -633,7 +645,7 @@ static void EPOUT_ready(unsigned int endpoint) /* It we're done, clean up */ if (size < ep->fifo_size || ep->received >= ep->length) { ep->use_dma = -1; - usb_core_transfer_complete(endpoint, USB_DIR_OUT, 0, ep->received); + complete_transfer(endpoint, USB_DIR_OUT, 0, ep->received); ep_transfer_completed(ep); logf("DMA RX transfer_complete"); return; @@ -704,9 +716,9 @@ static void setup_endpoint(struct usb_endpoint *ep) if (ep->wait) semaphore_release(&ep->complete); else - usb_core_transfer_complete(endpoint, USB_DIR_IN, -1, 0); + complete_transfer(endpoint, USB_DIR_IN, -1, 0); } else { - usb_core_transfer_complete(endpoint, USB_DIR_OUT, -1, 0); + complete_transfer(endpoint, USB_DIR_OUT, -1, 0); } } @@ -796,7 +808,7 @@ static void udc_reset(void) if (endpoints[0].wait) semaphore_release(&endpoints[0].complete); else - usb_core_transfer_complete(EP_CONTROL, USB_DIR_IN, -1, 0); + complete_transfer(EP_CONTROL, USB_DIR_IN, -1, 0); } endpoints[0].busy = false; @@ -806,7 +818,7 @@ static void udc_reset(void) endpoints[0].allocated = true; if (endpoints[1].busy) - usb_core_transfer_complete(EP_CONTROL, USB_DIR_OUT, -1, 0); + complete_transfer(EP_CONTROL, USB_DIR_OUT, -1, 0); endpoints[1].busy = false; endpoints[1].wait = false; @@ -825,7 +837,8 @@ static void udc_reset(void) REG_USB_INTRINE |= USB_INTR_EP(0); REG_USB_INTRUSBE |= USB_INTR_RESET; - usb_core_bus_reset(); + address_status = false; + usb_core_bus_reset(); } /* Interrupt handler */ @@ -1018,13 +1031,6 @@ void usb_drv_exit(void) REG_USB_POWER &= ~USB_POWER_SOFTCONN; } -void usb_drv_set_address(int address) -{ - logf("%s(%d)", __func__, address); - - REG_USB_FADDR = address; -} - static void usb_drv_send_internal(struct usb_endpoint* ep, void* ptr, int length, bool blocking) { int flags = disable_irq_save(); @@ -1171,11 +1177,11 @@ void usb_drv_cancel_all_transfers(void) if (endpoints[i].busy) { if (i & 1) - usb_core_transfer_complete(i >> 1, USB_DIR_OUT, -1, 0); + complete_transfer(i >> 1, USB_DIR_OUT, -1, 0); else if (endpoints[i].wait) semaphore_release(&endpoints[i].complete); else - usb_core_transfer_complete(i >> 1, USB_DIR_IN, -1, 0); + complete_transfer(i >> 1, USB_DIR_IN, -1, 0); } if(i != 1) /* ep0 out needs special handling */ @@ -1217,3 +1223,18 @@ void usb_drv_ep_deinit(const struct usb_drv_ep_alloc_ctx* ctx, int ep) else REG_USB_INTROUTE &= ~USB_INTR_EP(num); } + +static bool handle_set_address(struct usb_ctrlrequest *req) +{ + address_status = false; + if(!usb_drv_is_set_address(req)) + return false; + + const uint8_t address = req->wValue & 0x7f; + address_status = true; + usb_drv_cancel_all_transfers(); + usb_drv_send_nonblocking(EP_CONTROL, NULL, 0); + REG_USB_FADDR = address; + usb_core_notify_set_address(address); + return true; +} diff --git a/firmware/target/mips/ingenic_x1000/aic-x1000.c b/firmware/target/mips/ingenic_x1000/aic-x1000.c index f00239f285..ee678d2235 100644 --- a/firmware/target/mips/ingenic_x1000/aic-x1000.c +++ b/firmware/target/mips/ingenic_x1000/aic-x1000.c @@ -20,7 +20,7 @@ #include "system.h" #include "aic-x1000.h" -#include "gpio-x1000.h" +#include "gpio-ingenic.h" #include "x1000/aic.h" #include "x1000/cpm.h" diff --git a/firmware/target/mips/ingenic_x1000/boot-x1000.c b/firmware/target/mips/ingenic_x1000/boot-x1000.c index 078541054c..8fc73ede0d 100644 --- a/firmware/target/mips/ingenic_x1000/boot-x1000.c +++ b/firmware/target/mips/ingenic_x1000/boot-x1000.c @@ -21,8 +21,8 @@ #include "system.h" #include "usb.h" #include "boot-x1000.h" -#include "nand-x1000.h" -#include "gpio-x1000.h" +#include "nand-ingenic.h" +#include "gpio-ingenic.h" #include "clk-x1000.h" #include "x1000/cpm.h" #include "x1000/lcd.h" diff --git a/firmware/target/mips/ingenic_x1000/debug-x1000.c b/firmware/target/mips/ingenic_x1000/debug-x1000.c index dae9d675a8..2e2975a29d 100644 --- a/firmware/target/mips/ingenic_x1000/debug-x1000.c +++ b/firmware/target/mips/ingenic_x1000/debug-x1000.c @@ -28,7 +28,7 @@ #include "list.h" #include "clk-x1000.h" -#include "gpio-x1000.h" +#include "gpio-ingenic.h" static bool dbg_clocks(void) { diff --git a/firmware/target/mips/ingenic_x1000/erosqnative/audiohw-erosqnative.c b/firmware/target/mips/ingenic_x1000/erosqnative/audiohw-erosqnative.c index 8bab20abfa..9cb91bc55a 100644 --- a/firmware/target/mips/ingenic_x1000/erosqnative/audiohw-erosqnative.c +++ b/firmware/target/mips/ingenic_x1000/erosqnative/audiohw-erosqnative.c @@ -20,6 +20,7 @@ #include "system.h" #include "audiohw.h" +#include "pcm_sink.h" #include "pcm_sw_volume.h" #include "pcm_sampr.h" #include "i2c-target.h" @@ -29,8 +30,8 @@ #include "logf.h" #include "aic-x1000.h" -#include "i2c-x1000.h" -#include "gpio-x1000.h" +#include "i2c-ingenic.h" +#include "gpio-ingenic.h" #include "devicedata.h" /* @@ -102,11 +103,19 @@ void audiohw_init(void) } #endif es9018k2m_present_flag = true; + builtin_pcm_sink.caps.volume_type = PCM_SINK_HWVOL; - /* Default is 32-bit data, and it works ok. Enabling the following - * causes issue. Which is weird, I definitely thought AIC was configured - * for 24-bit data... */ - // es9018k2m_write_reg(ES9018K2M_REG1_INPUT_CONFIG, 0b01001100); // 24-bit data + /* + * Set sample depth to 32-bit. The sample depth setting corresponds + * to the I2S frame length which is twice the sample depth (since + * I2S is stereo). The AIC only supports 64-bit frames (see 6.3.1 + * "6.3.1 I2S and MSB-justified serial audio format" in the PM). + * + * Commented out since this is the hardware default. We technically + * do not need SPDIF/DSD detection so the lower nibble could be 0, + * but it was historically enabled does not seem to cause any harm. + */ + //es92018k2m_write_reg(ES9018K2M_REG1_INPUT_CONFIG, 0xcc); /* Datasheet: Sets the number os FSR edges that must occur before * * the DPLL and ASRC can lock on to the the incoming Signal. * @@ -192,9 +201,6 @@ void audiohw_set_volume(int vol_l, int vol_r) l = l <= PCM5102A_VOLUME_MIN ? PCM_MUTE_LEVEL : l; r = r <= PCM5102A_VOLUME_MIN ? PCM_MUTE_LEVEL : r; - /* set software volume just below unity due to - * DAC offset. We don't want to overflow the PCM system. */ - pcm_set_master_volume(-1, -1); es9018k2m_set_volume_async(l, r); } else /* PCM5102A */ diff --git a/firmware/target/mips/ingenic_x1000/erosqnative/button-erosqnative.c b/firmware/target/mips/ingenic_x1000/erosqnative/button-erosqnative.c index eeaff810d4..f666513503 100644 --- a/firmware/target/mips/ingenic_x1000/erosqnative/button-erosqnative.c +++ b/firmware/target/mips/ingenic_x1000/erosqnative/button-erosqnative.c @@ -24,9 +24,9 @@ #include "powermgmt.h" #include "panic.h" #include "axp-pmu.h" -#include "gpio-x1000.h" +#include "gpio-ingenic.h" #include "irq-x1000.h" -#include "i2c-x1000.h" +#include "i2c-ingenic.h" #include "eros_qn_codec.h" #include #include diff --git a/firmware/target/mips/ingenic_x1000/erosqnative/lcd-erosqnative.c b/firmware/target/mips/ingenic_x1000/erosqnative/lcd-erosqnative.c index e19eefe275..73bbb9437c 100644 --- a/firmware/target/mips/ingenic_x1000/erosqnative/lcd-erosqnative.c +++ b/firmware/target/mips/ingenic_x1000/erosqnative/lcd-erosqnative.c @@ -21,7 +21,7 @@ #include "lcd.h" #include "kernel.h" #include "lcd-x1000.h" -#include "gpio-x1000.h" +#include "gpio-ingenic.h" #include "system.h" #include "devicedata.h" diff --git a/firmware/target/mips/ingenic_x1000/erosqnative/power-erosqnative.c b/firmware/target/mips/ingenic_x1000/erosqnative/power-erosqnative.c index 6b9395c119..04e6572bb1 100644 --- a/firmware/target/mips/ingenic_x1000/erosqnative/power-erosqnative.c +++ b/firmware/target/mips/ingenic_x1000/erosqnative/power-erosqnative.c @@ -29,7 +29,7 @@ #endif #include "axp-pmu.h" #include "axp-2101.h" -#include "i2c-x1000.h" +#include "i2c-ingenic.h" #include "devicedata.h" unsigned short battery_level_disksafe = 3470; diff --git a/firmware/target/mips/ingenic_x1000/fiiom3k/audiohw-fiiom3k.c b/firmware/target/mips/ingenic_x1000/fiiom3k/audiohw-fiiom3k.c index 3e2b2fa204..5bb8420889 100644 --- a/firmware/target/mips/ingenic_x1000/fiiom3k/audiohw-fiiom3k.c +++ b/firmware/target/mips/ingenic_x1000/fiiom3k/audiohw-fiiom3k.c @@ -23,8 +23,8 @@ #include "system.h" #include "pcm_sampr.h" #include "aic-x1000.h" -#include "i2c-x1000.h" -#include "gpio-x1000.h" +#include "i2c-ingenic.h" +#include "gpio-ingenic.h" #include "logf.h" static int cur_audio_source = AUDIO_SRC_PLAYBACK; diff --git a/firmware/target/mips/ingenic_x1000/fiiom3k/button-fiiom3k.c b/firmware/target/mips/ingenic_x1000/fiiom3k/button-fiiom3k.c index e27fa08d36..fa18b4b109 100644 --- a/firmware/target/mips/ingenic_x1000/fiiom3k/button-fiiom3k.c +++ b/firmware/target/mips/ingenic_x1000/fiiom3k/button-fiiom3k.c @@ -25,9 +25,9 @@ #include "panic.h" #include "axp-pmu.h" #include "ft6x06.h" -#include "gpio-x1000.h" +#include "gpio-ingenic.h" #include "irq-x1000.h" -#include "i2c-x1000.h" +#include "i2c-ingenic.h" #include #include diff --git a/firmware/target/mips/ingenic_x1000/fiiom3k/lcd-fiiom3k.c b/firmware/target/mips/ingenic_x1000/fiiom3k/lcd-fiiom3k.c index a4f051bd27..3b6772c616 100644 --- a/firmware/target/mips/ingenic_x1000/fiiom3k/lcd-fiiom3k.c +++ b/firmware/target/mips/ingenic_x1000/fiiom3k/lcd-fiiom3k.c @@ -21,7 +21,7 @@ #include "lcd.h" #include "kernel.h" #include "lcd-x1000.h" -#include "gpio-x1000.h" +#include "gpio-ingenic.h" #include "system.h" static const uint32_t fiio_lcd_cmd_enable[] = { diff --git a/firmware/target/mips/ingenic_x1000/fiiom3k/power-fiiom3k.c b/firmware/target/mips/ingenic_x1000/fiiom3k/power-fiiom3k.c index a992c3de45..210a1ede2b 100644 --- a/firmware/target/mips/ingenic_x1000/fiiom3k/power-fiiom3k.c +++ b/firmware/target/mips/ingenic_x1000/fiiom3k/power-fiiom3k.c @@ -26,7 +26,7 @@ # include "usb_core.h" #endif #include "axp-pmu.h" -#include "i2c-x1000.h" +#include "i2c-ingenic.h" unsigned short battery_level_disksafe = 3470; diff --git a/firmware/target/mips/ingenic_x1000/lcd-x1000.c b/firmware/target/mips/ingenic_x1000/lcd-x1000.c index e8139d377d..754d8542d6 100644 --- a/firmware/target/mips/ingenic_x1000/lcd-x1000.c +++ b/firmware/target/mips/ingenic_x1000/lcd-x1000.c @@ -22,7 +22,7 @@ #include "system.h" #include "kernel.h" #include "lcd-x1000.h" -#include "dma-x1000.h" +#include "dma-ingenic.h" #include "irq-x1000.h" #include "x1000/lcd.h" #include "x1000/cpm.h" diff --git a/firmware/target/mips/ingenic_x1000/msc-x1000.c b/firmware/target/mips/ingenic_x1000/msc-x1000.c index 0ce6ff4990..cf79de2d03 100644 --- a/firmware/target/mips/ingenic_x1000/msc-x1000.c +++ b/firmware/target/mips/ingenic_x1000/msc-x1000.c @@ -6,7 +6,7 @@ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * - * Copyright (C) 2021 Aidan MacDonald + * Copyright (C) 2021-2026 Aidan MacDonald * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -17,12 +17,11 @@ * KIND, either express or implied. * ****************************************************************************/ - #include "system.h" #include "panic.h" #include "led.h" #include "msc-x1000.h" -#include "gpio-x1000.h" +#include "gpio-ingenic.h" #include "irq-x1000.h" #include "clk-x1000.h" #include "x1000/msc.h" @@ -30,901 +29,368 @@ #include #include -/* #define LOGF_ENABLE */ -#include "logf.h" - -/* TODO - this needs some auditing to better handle errors - * - * There should be a clearer code path involving errors. Especially we should - * ensure that removing the card always resets the driver to a sane state. +/* + * TODO: Clock helpers could be factored out of this file */ -#define DEBOUNCE_TIME (HZ/10) - -static const msc_config msc_configs[] = { -#if defined(FIIO_M3K) -#define MSC_CLOCK_SOURCE X1000_CLK_SCLK_A - { - .msc_nr = 0, - .msc_type = MSC_TYPE_SD, - .bus_width = 4, - .label = "microSD", - .cd_gpio = GPIO_MSC0_CD, - .cd_active_level = 0, - }, -#elif defined(SHANLING_Q1) -#define MSC_CLOCK_SOURCE X1000_CLK_MPLL - { - .msc_nr = 0, - .msc_type = MSC_TYPE_SD, - .bus_width = 4, - .label = "microSD", - .cd_gpio = GPIO_MSC0_CD, - .cd_active_level = 0, - }, - /* NOTE: SDIO wifi card is on msc1 */ -#elif defined(EROS_QN) -#define MSC_CLOCK_SOURCE X1000_CLK_SCLK_A - { - .msc_nr = 0, - .msc_type = MSC_TYPE_SD, - .bus_width = 4, - .label = "microSD", - .cd_gpio = GPIO_MSC0_CD, - .cd_active_level = 0, - }, -#else -# error "Please add X1000 MSC config" -#endif - {.msc_nr = -1}, -}; - -static const msc_config* msc_lookup_config(int msc) +static uint32_t get_msc_clkgr_bit(int msc) { - for(int i = 0; i < MSC_COUNT; ++i) - if(msc_configs[i].msc_nr == msc) - return &msc_configs[i]; - return NULL; + if (msc == 0) + return BM_CPM_CLKGR_MSC0; + else + return BM_CPM_CLKGR_MSC1; } -static msc_drv msc_drivers[MSC_COUNT]; - -static void msc0_cd_interrupt(void); -static void msc1_cd_interrupt(void); - -/* --------------------------------------------------------------------------- - * Initialization - */ +static bool msc_is_gated(int msc) +{ + return REG_CPM_CLKGR & get_msc_clkgr_bit(msc); +} static void msc_gate_clock(int msc, bool gate) { - int bit; - if(msc == 0) - bit = BM_CPM_CLKGR_MSC0; + if (gate) + REG_CPM_CLKGR |= get_msc_clkgr_bit(msc); else - bit = BM_CPM_CLKGR_MSC1; - - if(gate) - REG_CPM_CLKGR |= bit; - else - REG_CPM_CLKGR &= ~bit; + REG_CPM_CLKGR &= ~get_msc_clkgr_bit(msc); } -static void msc_init_one(msc_drv* d, int msc) +static void msc_set_clock(int msc, uint32_t src_freq, uint32_t bus_freq) { - /* Lookup config */ - d->drive_nr = -1; - d->config = msc_lookup_config(msc); - if(!d->config) { - d->msc_nr = -1; - return; - } - - /* Initialize driver state */ - d->msc_nr = msc; - d->driver_flags = 0; - d->clk_status = 0; - d->cmdat_def = jz_orf(MSC_CMDAT, RTRG_V(GE32), TTRG_V(LE32)); - d->req = NULL; - d->iflag_done = 0; - d->card_present = 1; - d->card_present_last = 1; - d->req_running = 0; - mutex_init(&d->lock); - semaphore_init(&d->cmd_done, 1, 0); - - /* Ensure correct clock source */ - jz_writef(CPM_MSC0CDR, CE(1), CLKDIV(0), - CLKSRC(MSC_CLOCK_SOURCE == X1000_CLK_MPLL ? 1 : 0)); - while(jz_readf(CPM_MSC0CDR, BUSY)); - jz_writef(CPM_MSC0CDR, CE(0)); - - /* Initialize the hardware */ - msc_gate_clock(msc, false); - msc_full_reset(d); - system_enable_irq(msc == 0 ? IRQ_MSC0 : IRQ_MSC1); - - /* Setup the card detect IRQ */ - if(d->config->cd_gpio != GPIO_NONE) { - if(gpio_get_level(d->config->cd_gpio) != d->config->cd_active_level) { - d->card_present = 0; - d->card_present_last = 0; - } - - system_set_irq_handler(GPIO_TO_IRQ(d->config->cd_gpio), - msc == 0 ? msc0_cd_interrupt : msc1_cd_interrupt); - gpio_set_function(d->config->cd_gpio, GPIOF_IRQ_EDGE(1)); - gpio_flip_edge_irq(d->config->cd_gpio); - gpio_enable_irq(d->config->cd_gpio); - } -} - -void msc_init(void) -{ - /* Only do this once -- each storage subsystem calls us in its init */ - static bool done = false; - if(done) - return; - done = true; - - /* Set up each MSC driver according to msc_configs */ - for(int i = 0; i < MSC_COUNT; ++i) - msc_init_one(&msc_drivers[i], i); -} - -msc_drv* msc_get(int type, int index) -{ - for(int i = 0, m = 0; i < MSC_COUNT; ++i) { - if(msc_drivers[i].config == NULL) - continue; - if(type == MSC_TYPE_ANY || msc_drivers[i].config->msc_type == type) - if(index == m++) - return &msc_drivers[i]; - } - - return NULL; -} - -msc_drv* msc_get_by_drive(int drive_nr) -{ - for(int i = 0; i < MSC_COUNT; ++i) - if(msc_drivers[i].drive_nr == drive_nr) - return &msc_drivers[i]; - return NULL; -} - -void msc_lock(msc_drv* d) -{ - mutex_lock(&d->lock); -} - -void msc_unlock(msc_drv* d) -{ - mutex_unlock(&d->lock); -} - -void msc_full_reset(msc_drv* d) -{ - msc_lock(d); - msc_set_clock_mode(d, MSC_CLK_AUTOMATIC); - msc_set_speed(d, MSC_SPEED_INIT); - msc_set_width(d, 1); - msc_ctl_reset(d); - d->driver_flags = 0; - memset(&d->cardinfo, 0, sizeof(tCardInfo)); - msc_unlock(d); -} - -bool msc_card_detect(msc_drv* d) -{ - if(d->config->cd_gpio == GPIO_NONE) - return true; - - return gpio_get_level(d->config->cd_gpio) == d->config->cd_active_level; -} - -void msc_led_trigger(void) -{ - bool state = false; - for(int i = 0; i < MSC_COUNT; ++i) - if(msc_drivers[i].req_running) - state = true; - - led(state); -} - -/* --------------------------------------------------------------------------- - * Controller API - */ - -void msc_ctl_reset(msc_drv* d) -{ - /* Ingenic code suggests a reset changes clkrt */ - int clkrt = REG_MSC_CLKRT(d->msc_nr); - - /* Send reset -- bit is NOT self clearing */ - jz_overwritef(MSC_CTRL(d->msc_nr), RESET(1)); - udelay(100); - jz_writef(MSC_CTRL(d->msc_nr), RESET(0)); - - /* Verify reset in the status register */ - long deadline = current_tick + HZ; - while(jz_readf(MSC_STAT(d->msc_nr), IS_RESETTING) && - current_tick < deadline) { - sleep(1); - } - - /* Ensure the clock state is as expected */ - if(d->clk_status & MSC_CLKST_AUTO) - jz_writef(MSC_LPM(d->msc_nr), ENABLE(1)); - else if(d->clk_status & MSC_CLKST_ENABLE) - jz_overwritef(MSC_CTRL(d->msc_nr), CLOCK_V(START)); - else - jz_overwritef(MSC_CTRL(d->msc_nr), CLOCK_V(STOP)); - - /* Clear and mask interrupts */ - REG_MSC_IMASK(d->msc_nr) = 0xffffffff; - REG_MSC_IFLAG(d->msc_nr) = 0xffffffff; - - /* Restore clkrt */ - REG_MSC_CLKRT(d->msc_nr) = clkrt; -} - -void msc_set_clock_mode(msc_drv* d, int mode) -{ - int cur_mode = (d->clk_status & MSC_CLKST_AUTO) ? MSC_CLK_AUTOMATIC - : MSC_CLK_MANUAL; - if(mode == cur_mode) - return; - - d->clk_status &= ~MSC_CLKST_ENABLE; - if(mode == MSC_CLK_AUTOMATIC) { - d->clk_status |= MSC_CLKST_AUTO; - jz_writef(MSC_CTRL(d->msc_nr), CLOCK_V(STOP)); - jz_writef(MSC_LPM(d->msc_nr), ENABLE(1)); - } else { - d->clk_status &= ~MSC_CLKST_AUTO; - jz_writef(MSC_LPM(d->msc_nr), ENABLE(0)); - jz_writef(MSC_CTRL(d->msc_nr), CLOCK_V(STOP)); - } -} - -void msc_enable_clock(msc_drv* d, bool enable) -{ - if(d->clk_status & MSC_CLKST_AUTO) - return; - - bool is_enabled = (d->clk_status & MSC_CLKST_ENABLE); - if(enable == is_enabled) - return; - - if(enable) { - jz_writef(MSC_CTRL(d->msc_nr), CLOCK_V(START)); - d->clk_status |= MSC_CLKST_ENABLE; - } else { - jz_writef(MSC_CTRL(d->msc_nr), CLOCK_V(STOP)); - d->clk_status &= ~MSC_CLKST_ENABLE; - } -} - -void msc_set_speed(msc_drv* d, int rate) -{ - /* Shut down clock while we change frequencies */ - if(d->clk_status & MSC_CLKST_ENABLE) - jz_writef(MSC_CTRL(d->msc_nr), CLOCK_V(STOP)); - - /* Wait for clock to go idle */ - while(jz_readf(MSC_STAT(d->msc_nr), CLOCK_EN)) + /* Wait for clock to go idle from any ongoing operation */ + while (jz_readf(MSC_STAT(msc), CLOCK_EN)) sleep(1); - /* freq1 is output by MSCxDIV; freq2 is output by MSC_CLKRT */ - uint32_t freq1 = rate; - uint32_t freq2 = rate; - if(freq1 < MSC_SPEED_FAST) - freq1 = MSC_SPEED_FAST; - - /* Handle MSCxDIV */ - uint32_t src_freq = clk_get(MSC_CLOCK_SOURCE) / 2; - uint32_t div = clk_calc_div(src_freq, freq1); - if(d->msc_nr == 0) { + /* + * Handle MSCxDIV, the max CLKRT division factor is 1/128 + * which allows us to get a 400 KHz clock from up to 50 MHz + * so there is no point using a lower MSCxDIV clock. + */ + uint32_t mscdiv_freq = MAX(bus_freq, 50000000); + uint32_t div = clk_calc_div(src_freq / 2, mscdiv_freq); + if (msc == 0) + { jz_writef(CPM_MSC0CDR, CE(1), CLKDIV(div - 1)); while(jz_readf(CPM_MSC0CDR, BUSY)); jz_writef(CPM_MSC0CDR, CE(0)); - } else { + } + else + { jz_writef(CPM_MSC1CDR, CE(1), CLKDIV(div - 1)); while(jz_readf(CPM_MSC1CDR, BUSY)); jz_writef(CPM_MSC1CDR, CE(0)); } /* Handle MSC_CLKRT */ - uint32_t clkrt = clk_calc_shift(src_freq/div, freq2); - REG_MSC_CLKRT(d->msc_nr) = clkrt; + uint32_t clkrt = clk_calc_shift(src_freq / (2 * div), bus_freq); + REG_MSC_CLKRT(msc) = clkrt; - /* Handle frequency dependent timing settings + /* + * Handle frequency dependent timing settings * TODO - these settings might be SD specific... */ - uint32_t out_freq = (src_freq/div) >> clkrt; - if(out_freq > MSC_SPEED_FAST) { - jz_writef(MSC_LPM(d->msc_nr), + uint32_t out_freq = (src_freq / (2 * div)) >> clkrt; + if (out_freq > 25000000) + { + jz_writef(MSC_LPM(msc), DRV_SEL_V(RISE_EDGE_DELAY_QTR_PHASE), SMP_SEL_V(RISE_EDGE_DELAYED)); - jz_writef(MSC_CTRL2(d->msc_nr), SPEED_V(HIGHSPEED)); - } else { - jz_writef(MSC_LPM(d->msc_nr), + jz_writef(MSC_CTRL2(msc), SPEED_V(HIGHSPEED)); + } + else + { + jz_writef(MSC_LPM(msc), DRV_SEL_V(FALL_EDGE), SMP_SEL_V(RISE_EDGE)); - jz_writef(MSC_CTRL2(d->msc_nr), SPEED_V(DEFAULT)); + jz_writef(MSC_CTRL2(msc), SPEED_V(DEFAULT)); } - - /* Restart clock if it was running before */ - if(d->clk_status & MSC_CLKST_ENABLE) - jz_writef(MSC_CTRL(d->msc_nr), CLOCK_V(START)); } -void msc_set_width(msc_drv* d, int width) +void x1000_msc_init(struct x1000_msc_controller* ctl, + struct x1000_msc_dma_desc *dma_desc, + int msc_nr, uint32_t src_clk_freq) { - /* Bus width is controlled per command with MSC_CMDAT. */ - if(width == 8) - jz_vwritef(d->cmdat_def, MSC_CMDAT, BUS_WIDTH_V(8BIT)); - else if(width == 4) - jz_vwritef(d->cmdat_def, MSC_CMDAT, BUS_WIDTH_V(4BIT)); + memset(ctl, 0, sizeof(*ctl)); + + ctl->msc_nr = msc_nr; + ctl->src_clk_freq = src_clk_freq; + ctl->dma_desc = dma_desc; + ctl->cmdat_def = jz_orf(MSC_CMDAT, RTRG_V(GE32), TTRG_V(LE32), INIT(1)); + + semaphore_init(&ctl->sem, 1, 0); + + /* Start off gated, sdmmc_host will power us on */ + msc_gate_clock(ctl->msc_nr, true); +} + +void x1000_msc_set_power_enabled(void *controller, bool enabled) +{ + struct x1000_msc_controller *ctl = controller; + + if (enabled) + { + msc_gate_clock(ctl->msc_nr, false); + + /* Controller reset */ + jz_overwritef(MSC_CTRL(ctl->msc_nr), RESET(1)); + udelay(100); + jz_writef(MSC_CTRL(ctl->msc_nr), RESET(0)); + while (jz_readf(MSC_STAT(ctl->msc_nr), IS_RESETTING)); + + /* Ensure interrupt state is clear */ + REG_MSC_IMASK(ctl->msc_nr) = 0xFFFFFFFFu; + REG_MSC_IFLAG(ctl->msc_nr) = 0xFFFFFFFFu; + + /* Set requested bus clock frequency */ + msc_set_clock(ctl->msc_nr, ctl->src_clk_freq, + sdmmc_host_get_bus_freq(ctl->bus_clock)); + + /* Set INIT bit and enable auto clock management */ + ctl->cmdat_def |= BM_MSC_CMDAT_INIT; + jz_writef(MSC_LPM(ctl->msc_nr), ENABLE(1)); + } else - jz_vwritef(d->cmdat_def, MSC_CMDAT, BUS_WIDTH_V(1BIT)); -} + { + /* Disable and gate clock */ + jz_writef(MSC_LPM(ctl->msc_nr), ENABLE(0)); + jz_writef(MSC_CTRL(ctl->msc_nr), CLOCK_V(STOP)); -/* --------------------------------------------------------------------------- - * Request API - */ - -/* Note -- this must only be called with IRQs disabled */ -static void msc_finish_request(msc_drv* d, int status) -{ - REG_MSC_IMASK(d->msc_nr) = 0xffffffff; - REG_MSC_IFLAG(d->msc_nr) = 0xffffffff; - if(d->req->flags & MSC_RF_DATA) - jz_writef(MSC_DMAC(d->msc_nr), ENABLE(0)); - - d->req->status = status; - d->req_running = 0; - d->iflag_done = 0; - - msc_led_trigger(); - timeout_cancel(&d->cmd_tmo); - semaphore_release(&d->cmd_done); -} - -static int msc_req_timeout(struct timeout* tmo) -{ - msc_drv* d = (msc_drv*)tmo->data; - msc_async_abort(d, MSC_REQ_LOCKUP); - return 0; -} - -void msc_async_start(msc_drv* d, msc_req* r) -{ - /* Determined needed cmdat and interrupts */ - unsigned cmdat = d->cmdat_def; - d->iflag_done = jz_orm(MSC_IFLAG, END_CMD_RES); - - cmdat |= jz_orf(MSC_CMDAT, RESP_FMT(r->resptype & ~MSC_RESP_BUSY)); - if(r->resptype & MSC_RESP_BUSY) - cmdat |= jz_orm(MSC_CMDAT, BUSY); - - if(r->flags & MSC_RF_INIT) - cmdat |= jz_orm(MSC_CMDAT, INIT); - - if(r->flags & MSC_RF_DATA) { - cmdat |= jz_orm(MSC_CMDAT, DATA_EN); - if(r->flags & MSC_RF_PROG) - d->iflag_done = jz_orm(MSC_IFLAG, WR_ALL_DONE); - else - d->iflag_done = jz_orm(MSC_IFLAG, DMA_DATA_DONE); + msc_gate_clock(ctl->msc_nr, true); } +} - if(r->flags & MSC_RF_WRITE) - cmdat |= jz_orm(MSC_CMDAT, WRITE_READ); +void x1000_msc_set_bus_width(void *controller, uint32_t width) +{ + struct x1000_msc_controller *ctl = controller; - if(r->flags & MSC_RF_AUTO_CMD12) - cmdat |= jz_orm(MSC_CMDAT, AUTO_CMD12); + switch (width) + { + case SDMMC_BUS_WIDTH_1BIT: + jz_vwritef(ctl->cmdat_def, MSC_CMDAT, BUS_WIDTH_V(1BIT)); + break; - if(r->flags & MSC_RF_ABORT) - cmdat |= jz_orm(MSC_CMDAT, IO_ABORT); + case SDMMC_BUS_WIDTH_4BIT: + jz_vwritef(ctl->cmdat_def, MSC_CMDAT, BUS_WIDTH_V(4BIT)); + break; - unsigned imask = jz_orm(MSC_IMASK, + case SDMMC_BUS_WIDTH_8BIT: + jz_vwritef(ctl->cmdat_def, MSC_CMDAT, BUS_WIDTH_V(8BIT)); + break; + + default: + panicf("%s", __func__); + } +} + +void x1000_msc_set_bus_clock(void *controller, uint32_t clock) +{ + struct x1000_msc_controller *ctl = controller; + uint32_t bus_freq = sdmmc_host_get_bus_freq(clock); + + ctl->bus_clock = clock; + + if (!msc_is_gated(ctl->msc_nr)) + msc_set_clock(ctl->msc_nr, ctl->src_clk_freq, bus_freq); +} + +int x1000_msc_submit_command(void *controller, + const struct sdmmc_host_command *cmd, + struct sdmmc_host_response *resp) +{ + struct x1000_msc_controller *ctl = controller; + uint32_t cmdat = ctl->cmdat_def; + uint32_t imask = jz_orm(MSC_IMASK, CRC_RES_ERROR, CRC_READ_ERROR, CRC_WRITE_ERROR, TIME_OUT_RES, TIME_OUT_READ, END_CMD_RES); - imask |= d->iflag_done; - /* Program the controller */ - if(r->flags & MSC_RF_DATA) { - REG_MSC_NOB(d->msc_nr) = r->nr_blocks; - REG_MSC_BLKLEN(d->msc_nr) = r->block_len; + void *buff_addr = cmd->buffer; + size_t buff_size = cmd->nr_blocks * cmd->block_len; + + /* INIT is only sent for the first command after power up */ + jz_vwritef(ctl->cmdat_def, MSC_CMDAT, INIT(0)); + + /* Response type setting */ + if (cmd->flags & SDMMC_RESP_BUSY) + jz_vwritef(cmdat, MSC_CMDAT, BUSY(1)); + + switch (SDMMC_RESP_LENGTH(cmd->flags)) + { + case SDMMC_RESP_NONE: + jz_vwritef(cmdat, MSC_CMDAT, RESP_FMT(0)); + + ctl->resp_len = 0; + break; + + case SDMMC_RESP_SHORT: + if (cmd->flags & SDMMC_RESP_NOCRC) + jz_vwritef(cmdat, MSC_CMDAT, RESP_FMT(3)); + else + jz_vwritef(cmdat, MSC_CMDAT, RESP_FMT(1)); + + ctl->resp_len = 1; + break; + + case SDMMC_RESP_LONG: + jz_vwritef(cmdat, MSC_CMDAT, RESP_FMT(2)); + ctl->resp_len = 4; + break; + + default: + panicf("%s", __func__); + break; } - REG_MSC_CMD(d->msc_nr) = r->command; - REG_MSC_ARG(d->msc_nr) = r->argument; - REG_MSC_CMDAT(d->msc_nr) = cmdat; + /* Data transfer setup */ + if (SDMMC_DATA_PRESENT(cmd->flags)) + { + if ((uintptr_t)buff_addr & (CACHEALIGN_SIZE - 1)) + panicf("%s: unaligned buffer", __func__); - REG_MSC_IFLAG(d->msc_nr) = imask; - REG_MSC_IMASK(d->msc_nr) &= ~imask; + ctl->dma_desc->nda = 0; + ctl->dma_desc->mem = PHYSADDR(buff_addr); + ctl->dma_desc->len = buff_size; + ctl->dma_desc->cmd = 2; /* ID=0, ENDI=1, LINK=0 */ + commit_dcache_range(ctl->dma_desc, sizeof(*ctl->dma_desc)); - if(r->flags & MSC_RF_DATA) { - d->dma_desc.nda = 0; - d->dma_desc.mem = PHYSADDR(r->data); - d->dma_desc.len = r->nr_blocks * r->block_len; - d->dma_desc.cmd = 2; /* ID=0, ENDI=1, LINK=0 */ - commit_dcache_range(&d->dma_desc, sizeof(d->dma_desc)); + if (SDMMC_DATA_DIR(cmd->flags) == SDMMC_DATA_WRITE) + { + commit_dcache_range(buff_addr, buff_size); - if(r->flags & MSC_RF_WRITE) - commit_dcache_range(r->data, d->dma_desc.len); + jz_vwritef(cmdat, MSC_CMDAT, WRITE_READ(1)); + ctl->iflag_done = jz_orm(MSC_IMASK, WR_ALL_DONE); + } else - discard_dcache_range(r->data, d->dma_desc.len); + { + discard_dcache_range(buff_addr, buff_size); - /* Unaligned address for DMA doesn't seem to work correctly. - * FAT FS driver should ensure proper alignment of all buffers, - * so in practice this panic should not occur, but if it does - * I want to hear about it. */ - if(UNLIKELY(d->dma_desc.mem & 3)) { - panicf("msc%d bad align: %08x", d->msc_nr, - (unsigned)d->dma_desc.mem); + ctl->iflag_done = jz_orm(MSC_IMASK, DMA_DATA_DONE); } - jz_writef(MSC_DMAC(d->msc_nr), MODE_SEL(0), INCR(0), DMASEL(0)); - REG_MSC_DMANDA(d->msc_nr) = PHYSADDR(&d->dma_desc); + jz_vwritef(cmdat, MSC_CMDAT, DATA_EN(1)); + + jz_writef(MSC_DMAC(ctl->msc_nr), MODE_SEL(0), INCR(0), DMASEL(0)); + REG_MSC_DMANDA(ctl->msc_nr) = PHYSADDR(ctl->dma_desc); + } + else + { + /* + * PROG_DONE is actually waiting for the busy signal so is + * required for all commands with R1b response (like CMD12). + * For writes, the WR_ALL_DONE interrupt subsumes PROG_DONE. + */ + if (cmd->flags & SDMMC_RESP_BUSY) + ctl->iflag_done = jz_orm(MSC_IMASK, PROG_DONE); + else + ctl->iflag_done = jz_orm(MSC_IMASK, END_CMD_RES); } - /* Begin processing */ - d->req = r; - d->req_running = 1; - msc_led_trigger(); - jz_writef(MSC_CTRL(d->msc_nr), START_OP(1)); - if(r->flags & MSC_RF_DATA) - jz_writef(MSC_DMAC(d->msc_nr), ENABLE(1)); + REG_MSC_NOB(ctl->msc_nr) = cmd->nr_blocks; + REG_MSC_BLKLEN(ctl->msc_nr) = cmd->block_len; + REG_MSC_CMD(ctl->msc_nr) = cmd->command; + REG_MSC_ARG(ctl->msc_nr) = cmd->argument; + REG_MSC_CMDAT(ctl->msc_nr) = cmdat; - /* TODO: calculate a suitable lower value for the lockup timeout. - * - * The SD spec defines timings based on the number of blocks transferred, - * see sec. 4.6.2 "Read, write, and erase timeout conditions". This should - * reduce the long delays which happen if errors occur. - * - * Also need to check if registers MSC_RDTO / MSC_RESTO are correctly set. - */ - timeout_register(&d->cmd_tmo, msc_req_timeout, 10*HZ, (intptr_t)d); + imask |= ctl->iflag_done; + REG_MSC_IFLAG(ctl->msc_nr) = imask; + REG_MSC_IMASK(ctl->msc_nr) &= ~imask; + + ctl->resp = resp; + ctl->err_code = SDMMC_STATUS_OK; + membarrier(); + + jz_writef(MSC_CTRL(ctl->msc_nr), START_OP(1)); + if (SDMMC_DATA_PRESENT(cmd->flags)) + jz_writef(MSC_DMAC(ctl->msc_nr), ENABLE(1)); + + semaphore_wait(&ctl->sem, TIMEOUT_BLOCK); + + if (SDMMC_DATA_PRESENT(cmd->flags) && + SDMMC_DATA_DIR(cmd->flags) == SDMMC_DATA_READ) + discard_dcache_range(buff_addr, buff_size); + + return ctl->err_code; } -void msc_async_abort(msc_drv* d, int status) +static void x1000_msc_finish_command(struct x1000_msc_controller *ctl) { + REG_MSC_IMASK(ctl->msc_nr) = 0xFFFFFFFFu; + REG_MSC_IFLAG(ctl->msc_nr) = 0xFFFFFFFFu; + jz_writef(MSC_DMAC(ctl->msc_nr), ENABLE(0)); + semaphore_release(&ctl->sem); +} + +void x1000_msc_abort_command(void *controller) +{ + struct x1000_msc_controller *ctl = controller; int irq = disable_irq_save(); - if(d->req_running) { - logf("msc%d: async abort status:%d", d->msc_nr, status); - msc_finish_request(d, status); - } + + /* + * Looks strange but this wait is always OK because it either + * acquires the semaphore of an idle controller or is a no-op + * if there is a command running. + * + * Finishing the command then releases the semaphore which is + * correct for both cases. + */ + semaphore_wait(&ctl->sem, TIMEOUT_NOBLOCK); + + ctl->err_code = SDMMC_STATUS_ERROR; + x1000_msc_finish_command(ctl); restore_irq(irq); } -int msc_async_wait(msc_drv* d, int timeout) +static void x1000_msc_read_response(struct x1000_msc_controller *ctl) { - if(semaphore_wait(&d->cmd_done, timeout) == OBJ_WAIT_TIMEDOUT) - return MSC_REQ_INCOMPLETE; + uint32_t res = REG_MSC_RES(ctl->msc_nr); + uint32_t dat; - return d->req->status; -} - -int msc_request(msc_drv* d, msc_req* r) -{ - msc_async_start(d, r); - return msc_async_wait(d, TIMEOUT_BLOCK); -} - -/* --------------------------------------------------------------------------- - * Command response handling - */ - -static void msc_read_response(msc_drv* d) -{ - unsigned res = REG_MSC_RES(d->msc_nr); - unsigned dat; - switch(d->req->resptype) { - case MSC_RESP_R1: - case MSC_RESP_R1B: - case MSC_RESP_R3: - case MSC_RESP_R6: - case MSC_RESP_R7: - dat = res << 24; - res = REG_MSC_RES(d->msc_nr); - dat |= res << 8; - res = REG_MSC_RES(d->msc_nr); - dat |= res & 0xff; - d->req->response[0] = dat; - break; - - case MSC_RESP_R2: - for(int i = 0; i < 4; ++i) { + if (ctl->resp_len == 4) + { + for (int i = 0; i < 4; ++i) + { dat = res << 24; - res = REG_MSC_RES(d->msc_nr); + res = REG_MSC_RES(ctl->msc_nr); dat |= res << 8; - res = REG_MSC_RES(d->msc_nr); + res = REG_MSC_RES(ctl->msc_nr); dat |= res >> 8; - d->req->response[i] = dat; - } - break; - - default: - return; - } -} - -static int msc_check_sd_response(msc_drv* d) -{ - if(d->req->resptype == MSC_RESP_R1 || - d->req->resptype == MSC_RESP_R1B) { - if(d->req->response[0] & SD_R1_CARD_ERROR) { - logf("msc%d: R1 card error: %08x", d->msc_nr, d->req->response[0]); - return MSC_REQ_CARD_ERR; + if (ctl->resp) + ctl->resp->data[i] = dat; } } + else if (ctl->resp_len == 1) + { + dat = res << 24; + res = REG_MSC_RES(ctl->msc_nr); + dat |= res << 8; + res = REG_MSC_RES(ctl->msc_nr); + dat |= res & 0xff; - return MSC_REQ_SUCCESS; -} - -static int msc_check_response(msc_drv* d) -{ - switch(d->config->msc_type) { - case MSC_TYPE_SD: - return msc_check_sd_response(d); - default: - /* TODO - implement msc_check_response for MMC and CE-ATA */ - return 0; + if (ctl->resp) + ctl->resp->data[0] = dat; } } -/* --------------------------------------------------------------------------- - * Interrupt handlers - */ - -static void msc_interrupt(msc_drv* d) +void x1000_msc_irq_handler(struct x1000_msc_controller *ctl) { - const unsigned tmo_bits = jz_orm(MSC_IFLAG, TIME_OUT_READ, TIME_OUT_RES); - const unsigned crc_bits = jz_orm(MSC_IFLAG, CRC_RES_ERROR, - CRC_READ_ERROR, CRC_WRITE_ERROR); - const unsigned err_bits = tmo_bits | crc_bits; + const uint32_t tmo_bits = jz_orm(MSC_IFLAG, TIME_OUT_READ, TIME_OUT_RES); + const uint32_t crc_bits = jz_orm(MSC_IFLAG, CRC_RES_ERROR, CRC_READ_ERROR, CRC_WRITE_ERROR); - unsigned iflag = REG_MSC_IFLAG(d->msc_nr) & ~REG_MSC_IMASK(d->msc_nr); - bool handled = false; + uint32_t iflag = REG_MSC_IFLAG(ctl->msc_nr); - /* In case card was removed */ - if(!msc_card_detect(d)) { - msc_finish_request(d, MSC_REQ_EXTRACTED); - return; + if (iflag & tmo_bits) + ctl->err_code = SDMMC_STATUS_TIMEOUT; + else if (iflag & crc_bits) + ctl->err_code = SDMMC_STATUS_INVALID_CRC; + + /* Read and clear command response */ + if (iflag & BM_MSC_IFLAG_END_CMD_RES) + { + REG_MSC_IMASK(ctl->msc_nr) |= BM_MSC_IFLAG_END_CMD_RES; + REG_MSC_IFLAG(ctl->msc_nr) = BM_MSC_IFLAG_END_CMD_RES; + x1000_msc_read_response(ctl); } - /* Check for errors */ - if(iflag & err_bits) { - int st; - if(iflag & crc_bits) - st = MSC_REQ_CRC_ERR; - else if(iflag & tmo_bits) - st = MSC_REQ_TIMEOUT; - else - st = MSC_REQ_ERROR; - - msc_finish_request(d, st); - return; - } - - /* Read and check the command response */ - if(iflag & BM_MSC_IFLAG_END_CMD_RES) { - msc_read_response(d); - int st = msc_check_response(d); - if(st == MSC_REQ_SUCCESS) { - jz_writef(MSC_IMASK(d->msc_nr), END_CMD_RES(1)); - jz_overwritef(MSC_IFLAG(d->msc_nr), END_CMD_RES(1)); - handled = true; - } else { - msc_finish_request(d, st); - return; - } - } - - /* Check if the "done" interrupt is signaled */ - if(iflag & d->iflag_done) { - /* Discard after DMA in case of hardware cache prefetching. - * Only needed for read operations. - */ - if((d->req->flags & MSC_RF_DATA) != 0 && - (d->req->flags & MSC_RF_WRITE) == 0) { - discard_dcache_range(d->req->data, - d->req->block_len * d->req->nr_blocks); - } - - msc_finish_request(d, MSC_REQ_SUCCESS); - return; - } - - if(!handled) { - panicf("msc%d: irq bug! iflag:%08x raw_iflag:%08lx imask:%08lx", - d->msc_nr, iflag, REG_MSC_IFLAG(d->msc_nr), REG_MSC_IMASK(d->msc_nr)); - } -} - -static int msc_cd_callback(struct timeout* tmo) -{ - msc_drv* d = (msc_drv*)tmo->data; - int now_present = msc_card_detect(d) ? 1 : 0; - - /* If the CD pin level changed during the timeout interval, then the - * signal is not yet stable and we need to wait longer. */ - if(now_present != d->card_present_last) { - d->card_present_last = now_present; - return DEBOUNCE_TIME; - } - - /* If there is a change, then broadcast the hotswap event */ - if(now_present != d->card_present) { - if(now_present) { - d->card_present = 1; - queue_broadcast(SYS_HOTSWAP_INSERTED, d->drive_nr); - } else { - msc_async_abort(d, MSC_REQ_EXTRACTED); - d->card_present = 0; - queue_broadcast(SYS_HOTSWAP_EXTRACTED, d->drive_nr); - } - } - - return 0; -} - -static void msc_cd_interrupt(msc_drv* d) -{ - /* Timer to debounce input */ - d->card_present_last = msc_card_detect(d) ? 1 : 0; - timeout_register(&d->cd_tmo, msc_cd_callback, DEBOUNCE_TIME, (intptr_t)d); - - /* Invert the IRQ */ - gpio_flip_edge_irq(d->config->cd_gpio); -} - -void MSC0(void) -{ - msc_interrupt(&msc_drivers[0]); -} - -void MSC1(void) -{ - msc_interrupt(&msc_drivers[1]); -} - -static void msc0_cd_interrupt(void) -{ - msc_cd_interrupt(&msc_drivers[0]); -} - -static void msc1_cd_interrupt(void) -{ - msc_cd_interrupt(&msc_drivers[1]); -} - -/* --------------------------------------------------------------------------- - * SD command helpers - */ - -int msc_cmd_exec(msc_drv* d, msc_req* r) -{ - int status = msc_request(d, r); - if(status == MSC_REQ_SUCCESS) - return status; - else if(status == MSC_REQ_LOCKUP || status == MSC_REQ_EXTRACTED) - d->driver_flags |= MSC_DF_ERRSTATE; - else if(r->flags & (MSC_RF_ERR_CMD12|MSC_RF_AUTO_CMD12)) { - /* After an error, the controller does not automatically issue CMD12, - * so we need to send it if it's needed, as required by the SD spec. - */ - msc_req nreq = {0}; - nreq.command = SD_STOP_TRANSMISSION; - nreq.resptype = MSC_RESP_R1B; - nreq.flags = MSC_RF_ABORT; - logf("msc%d: cmd%d error, sending cmd12", d->msc_nr, r->command); - if(msc_cmd_exec(d, &nreq)) - d->driver_flags |= MSC_DF_ERRSTATE; - } - - logf("msc%d: err:%d, cmd%d, arg:%x", d->msc_nr, status, - r->command, r->argument); - return status; -} - -int msc_app_cmd_exec(msc_drv* d, msc_req* r) -{ - msc_req areq = {0}; - areq.command = SD_APP_CMD; - areq.argument = d->cardinfo.rca; - areq.resptype = MSC_RESP_R1; - if(msc_cmd_exec(d, &areq)) - return areq.status; - - /* Verify that CMD55 was accepted */ - if((areq.response[0] & (1 << 5)) == 0) - return MSC_REQ_ERROR; - - return msc_cmd_exec(d, r); -} - -int msc_cmd_go_idle_state(msc_drv* d) -{ - msc_req req = {0}; - req.command = SD_GO_IDLE_STATE; - req.resptype = MSC_RESP_NONE; - req.flags = MSC_RF_INIT; - return msc_cmd_exec(d, &req); -} - -int msc_cmd_send_if_cond(msc_drv* d) -{ - msc_req req = {0}; - req.command = SD_SEND_IF_COND; - req.argument = 0x1aa; - req.resptype = MSC_RESP_R7; - - /* TODO - Check if SEND_IF_COND timeout is really an error - * IIRC, this can occur if the card isn't HCS (old cards < 2 GiB). - */ - if(msc_cmd_exec(d, &req)) - return req.status; - - /* Set HCS bit if the card responds correctly */ - if((req.response[0] & 0xff) == 0xaa) - d->driver_flags |= MSC_DF_HCS_CARD; - - return MSC_REQ_SUCCESS; -} - -int msc_cmd_app_op_cond(msc_drv* d) -{ - msc_req req = {0}; - req.command = SD_APP_OP_COND; - req.argument = 0x00300000; /* 3.4 - 3.6 V */ - req.resptype = MSC_RESP_R3; - if(d->driver_flags & MSC_DF_HCS_CARD) - req.argument |= (1 << 30); - - int timeout = 2 * HZ; - do { - if(msc_app_cmd_exec(d, &req)) - return req.status; - if(req.response[0] & (1 << 31)) - break; - sleep(1); - } while(--timeout > 0); - - if(timeout == 0) - return MSC_REQ_TIMEOUT; - - return MSC_REQ_SUCCESS; -} - -int msc_cmd_all_send_cid(msc_drv* d) -{ - msc_req req = {0}; - req.command = SD_ALL_SEND_CID; - req.resptype = MSC_RESP_R2; - if(msc_cmd_exec(d, &req)) - return req.status; - - for(int i = 0; i < 4; ++i) - d->cardinfo.cid[i] = req.response[i]; - - return MSC_REQ_SUCCESS; -} - -int msc_cmd_send_rca(msc_drv* d) -{ - msc_req req = {0}; - req.command = SD_SEND_RELATIVE_ADDR; - req.resptype = MSC_RESP_R6; - if(msc_cmd_exec(d, &req)) - return req.status; - - d->cardinfo.rca = req.response[0] & 0xffff0000; - return MSC_REQ_SUCCESS; -} - -int msc_cmd_send_csd(msc_drv* d) -{ - msc_req req = {0}; - req.command = SD_SEND_CSD; - req.argument = d->cardinfo.rca; - req.resptype = MSC_RESP_R2; - if(msc_cmd_exec(d, &req)) - return req.status; - - for(int i = 0; i < 4; ++i) - d->cardinfo.csd[i] = req.response[i]; - sd_parse_csd(&d->cardinfo); - - if(d->cardinfo.sd2plus) - d->driver_flags |= MSC_DF_V2_CARD; - - return 0; -} - -int msc_cmd_select_card(msc_drv* d) -{ - msc_req req = {0}; - req.command = SD_SELECT_CARD; - req.argument = d->cardinfo.rca; - req.resptype = MSC_RESP_R1B; - return msc_cmd_exec(d, &req); -} - -int msc_cmd_set_bus_width(msc_drv* d, int width) -{ - /* TODO - must we check bus width is supported in the cardinfo? */ - msc_req req = {0}; - req.command = SD_SET_BUS_WIDTH; - req.resptype = MSC_RESP_R1; - switch(width) { - case 1: req.argument = 0; break; - case 4: req.argument = 2; break; - default: return MSC_REQ_ERROR; - } - - if(msc_app_cmd_exec(d, &req)) - return req.status; - - msc_set_width(d, width); - return MSC_REQ_SUCCESS; -} - -int msc_cmd_set_clr_card_detect(msc_drv* d, int arg) -{ - msc_req req = {0}; - req.command = SD_SET_CLR_CARD_DETECT; - req.argument = arg; - req.resptype = MSC_RESP_R1; - return msc_app_cmd_exec(d, &req); -} - -int msc_cmd_switch_freq(msc_drv* d) -{ - /* If card doesn't support High Speed, we don't need to send a command */ - if((d->driver_flags & MSC_DF_V2_CARD) == 0) { - msc_set_speed(d, MSC_SPEED_FAST); - return MSC_REQ_SUCCESS; - } - - /* Try switching to High Speed (50 MHz) */ - char buffer[64] CACHEALIGN_ATTR; - msc_req req = {0}; - req.command = SD_SWITCH_FUNC; - req.argument = 0x80fffff1; - req.resptype = MSC_RESP_R1; - req.flags = MSC_RF_DATA; - req.data = &buffer[0]; - req.block_len = 64; - req.nr_blocks = 1; - if(msc_cmd_exec(d, &req)) - return req.status; - - msc_set_speed(d, MSC_SPEED_HIGH); - return MSC_REQ_SUCCESS; -} - -int msc_cmd_send_status(msc_drv* d) -{ - msc_req req = {0}; - req.command = SD_SEND_STATUS; - req.argument = d->cardinfo.rca; - req.resptype = MSC_RESP_R1; - return msc_cmd_exec(d, &req); -} - -int msc_cmd_set_block_len(msc_drv* d, unsigned len) -{ - msc_req req = {0}; - req.command = SD_SET_BLOCKLEN; - req.argument = len; - req.resptype = MSC_RESP_R1; - return msc_cmd_exec(d, &req); + if ((iflag & ctl->iflag_done) || ctl->err_code) + x1000_msc_finish_command(ctl); } diff --git a/firmware/target/mips/ingenic_x1000/msc-x1000.h b/firmware/target/mips/ingenic_x1000/msc-x1000.h index 60f15b749e..aaa7157ee3 100644 --- a/firmware/target/mips/ingenic_x1000/msc-x1000.h +++ b/firmware/target/mips/ingenic_x1000/msc-x1000.h @@ -17,168 +17,47 @@ * KIND, either express or implied. * ****************************************************************************/ - #ifndef __MSC_X1000_H__ #define __MSC_X1000_H__ -#include "kernel.h" -#include "sdmmc.h" -#include +#include "sdmmc_host.h" -/* Number of MSC controllers */ -#define MSC_COUNT 2 +/* Must be allocated on a cacheline boundary */ +struct x1000_msc_dma_desc +{ + uint32_t nda; + uint32_t mem; + uint32_t len; + uint32_t cmd; +}; -/* Media types */ -#define MSC_TYPE_SD 0 -#define MSC_TYPE_MMC 1 -#define MSC_TYPE_ATA 2 -#define MSC_TYPE_ANY 3 - -/* Clock modes */ -#define MSC_CLK_MANUAL 0 -#define MSC_CLK_AUTOMATIC 1 - -/* Clock status bits */ -#define MSC_CLKST_ENABLE (1 << 0) -#define MSC_CLKST_AUTO (1 << 1) - -/* Driver flags */ -#define MSC_DF_ERRSTATE (1 << 0) -#define MSC_DF_READY (1 << 1) -#define MSC_DF_HCS_CARD (1 << 2) -#define MSC_DF_V2_CARD (1 << 3) -#define MSC_DF_HAS_SBC (1 << 4) - -/* Request status codes */ -#define MSC_REQ_SUCCESS 0 -#define MSC_REQ_CRC_ERR 1 -#define MSC_REQ_CARD_ERR 2 -#define MSC_REQ_TIMEOUT 3 -#define MSC_REQ_EXTRACTED 4 -#define MSC_REQ_LOCKUP 5 -#define MSC_REQ_ERROR 6 -#define MSC_REQ_INCOMPLETE (-1) - -/* Response types */ -#define MSC_RESP_NONE 0 -#define MSC_RESP_BUSY (1 << 7) -#define MSC_RESP_R1 1 -#define MSC_RESP_R1B (MSC_RESP_R1|MSC_RESP_BUSY) -#define MSC_RESP_R2 2 -#define MSC_RESP_R3 3 -#define MSC_RESP_R6 6 -#define MSC_RESP_R7 7 - -/* Request flags */ -#define MSC_RF_INIT (1 << 0) -#define MSC_RF_ERR_CMD12 (1 << 1) -#define MSC_RF_AUTO_CMD12 (1 << 2) -#define MSC_RF_PROG (1 << 3) -#define MSC_RF_DATA (1 << 4) -#define MSC_RF_WRITE (1 << 5) -#define MSC_RF_ABORT (1 << 6) - -/* Clock speeds */ -#define MSC_SPEED_INIT 400000 -#define MSC_SPEED_FAST 25000000 -#define MSC_SPEED_HIGH 50000000 - -typedef struct msc_config { +struct x1000_msc_controller +{ int msc_nr; - int msc_type; - int bus_width; - const char* label; - int cd_gpio; - int cd_active_level; -} msc_config; + uint32_t src_clk_freq; + struct x1000_msc_dma_desc *dma_desc; -typedef struct msc_req { - /* Filled by caller */ - int command; - unsigned argument; - int resptype; - int flags; - void* data; - unsigned nr_blocks; - unsigned block_len; + uint32_t bus_clock; + uint32_t cmdat_def; + uint32_t iflag_done; + int resp_len; + int err_code; + struct sdmmc_host_response *resp; - /* Filled by driver */ - volatile unsigned response[4]; - volatile int status; -} msc_req; + struct semaphore sem; +}; -struct sd_dma_desc { - unsigned nda; - unsigned mem; - unsigned len; - unsigned cmd; -} __attribute__((aligned(16))); +void x1000_msc_init(struct x1000_msc_controller* ctl, + struct x1000_msc_dma_desc *dma_desc, + int msc_nr, uint32_t src_clk_freq); -typedef struct msc_drv { - int msc_nr; - int drive_nr; - const msc_config* config; - - int driver_flags; - int clk_status; - unsigned cmdat_def; - msc_req* req; - unsigned iflag_done; - - volatile int req_running; - volatile int card_present; /* Debounced status */ - volatile int card_present_last; /* Status when we last polled it */ - - struct mutex lock; - struct semaphore cmd_done; - struct timeout cmd_tmo; - struct timeout cd_tmo; - struct sd_dma_desc dma_desc; - - tCardInfo cardinfo; -} msc_drv; - -/* Driver initialization, etc */ -extern void msc_init(void); -extern msc_drv* msc_get(int type, int index); -extern msc_drv* msc_get_by_drive(int drive_nr); - -extern void msc_lock(msc_drv* d); -extern void msc_unlock(msc_drv* d); -extern void msc_full_reset(msc_drv* d); -extern bool msc_card_detect(msc_drv* d); - -extern void msc_led_trigger(void); - -/* Controller API */ -extern void msc_ctl_reset(msc_drv* d); -extern void msc_set_clock_mode(msc_drv* d, int mode); -extern void msc_enable_clock(msc_drv* d, bool enable); -extern void msc_set_speed(msc_drv* d, int rate); -extern void msc_set_width(msc_drv* d, int width); - -/* Request API */ -extern void msc_async_start(msc_drv* d, msc_req* r); -extern void msc_async_abort(msc_drv* d, int status); -extern int msc_async_wait(msc_drv* d, int timeout); -extern int msc_request(msc_drv* d, msc_req* r); - -/* Command helpers; note these are written with SD in mind - * and should be reviewed before using them for MMC / CE-ATA - */ -extern int msc_cmd_exec(msc_drv* d, msc_req* r); -extern int msc_app_cmd_exec(msc_drv* d, msc_req* r); -extern int msc_cmd_go_idle_state(msc_drv* d); -extern int msc_cmd_send_if_cond(msc_drv* d); -extern int msc_cmd_app_op_cond(msc_drv* d); -extern int msc_cmd_all_send_cid(msc_drv* d); -extern int msc_cmd_send_rca(msc_drv* d); -extern int msc_cmd_send_csd(msc_drv* d); -extern int msc_cmd_select_card(msc_drv* d); -extern int msc_cmd_set_bus_width(msc_drv* d, int width); -extern int msc_cmd_set_clr_card_detect(msc_drv* d, int arg); -extern int msc_cmd_switch_freq(msc_drv* d); -extern int msc_cmd_send_status(msc_drv* d); -extern int msc_cmd_set_block_len(msc_drv* d, unsigned len); +void x1000_msc_set_power_enabled(void *controller, bool enabled); +void x1000_msc_set_bus_width(void *controller, uint32_t width); +void x1000_msc_set_bus_clock(void *controller, uint32_t clock); +int x1000_msc_submit_command(void *controller, + const struct sdmmc_host_command *cmd, + struct sdmmc_host_response *resp); +void x1000_msc_abort_command(void *controller); +void x1000_msc_irq_handler(struct x1000_msc_controller *ctl); #endif /* __MSC_X1000_H__ */ diff --git a/firmware/target/mips/ingenic_x1000/pcm-x1000.c b/firmware/target/mips/ingenic_x1000/pcm-x1000.c index 24dc49a1a1..f877416aec 100644 --- a/firmware/target/mips/ingenic_x1000/pcm-x1000.c +++ b/firmware/target/mips/ingenic_x1000/pcm-x1000.c @@ -26,7 +26,7 @@ #include "pcm-internal.h" #include "pcm_sink.h" #include "panic.h" -#include "dma-x1000.h" +#include "dma-ingenic.h" #include "irq-x1000.h" #include "x1000/aic.h" #include "x1000/cpm.h" @@ -70,16 +70,21 @@ static void sink_dma_init(void) audiohw_init(); #if (PCM_NATIVE_BITDEPTH > 16) - /* Program audio format (stereo, 24 bit samples) */ - jz_writef(AIC_CCR, PACK16(0), CHANNEL_V(STEREO), - OSS_V(24BIT), ISS_V(24BIT), M2S(0)); - jz_writef(AIC_I2SCR, SWLH(0)); -#else - /* Program audio format (stereo, packed 16 bit samples) */ - jz_writef(AIC_CCR, PACK16(1), CHANNEL_V(STEREO), - OSS_V(16BIT), ISS_V(16BIT), M2S(0)); - jz_writef(AIC_I2SCR, SWLH(0)); + if (builtin_pcm_sink.caps.volume_type != PCM_SINK_HWVOL) + { + /* Program audio format (stereo, 24 bit samples) */ + jz_writef(AIC_CCR, PACK16(0), CHANNEL_V(STEREO), + OSS_V(24BIT), ISS_V(24BIT), M2S(0)); + jz_writef(AIC_I2SCR, SWLH(0)); + } + else #endif + { + /* Program audio format (stereo, packed 16 bit samples) */ + jz_writef(AIC_CCR, PACK16(1), CHANNEL_V(STEREO), + OSS_V(16BIT), ISS_V(16BIT), M2S(0)); + jz_writef(AIC_I2SCR, SWLH(0)); + } /* Set DMA settings */ jz_writef(AIC_CFG, TFTH(16), RFTH(15)); @@ -208,6 +213,7 @@ struct pcm_sink builtin_pcm_sink = { .samprs = hw_freq_sampr, .num_samprs = HW_NUM_FREQ, .default_freq = HW_FREQ_DEFAULT, + .volume_type = PCM_NATIVE_VOLUME_TYPE, }, .ops = { .init = sink_dma_init, diff --git a/firmware/target/mips/ingenic_x1000/pwm-x1000.c b/firmware/target/mips/ingenic_x1000/pwm-x1000.c index 8a111b484e..99b12d2a6c 100644 --- a/firmware/target/mips/ingenic_x1000/pwm-x1000.c +++ b/firmware/target/mips/ingenic_x1000/pwm-x1000.c @@ -20,7 +20,7 @@ #include "pwm-x1000.h" #include "clk-x1000.h" -#include "gpio-x1000.h" +#include "gpio-ingenic.h" #include "system.h" #include "kernel.h" #include "x1000/tcu.h" diff --git a/firmware/target/mips/ingenic_x1000/sd-x1000.c b/firmware/target/mips/ingenic_x1000/sd-x1000.c deleted file mode 100644 index e40db5833d..0000000000 --- a/firmware/target/mips/ingenic_x1000/sd-x1000.c +++ /dev/null @@ -1,281 +0,0 @@ -/*************************************************************************** - * __________ __ ___. - * Open \______ \ ____ ____ | | _\_ |__ _______ ___ - * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / - * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < - * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ - * \/ \/ \/ \/ \/ - * - * Copyright (C) 2021 Aidan MacDonald - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - ****************************************************************************/ - -#include "system.h" -#include "storage.h" -#include "sdmmc.h" -#include "sd.h" -#include "msc-x1000.h" -#include "gpio-x1000.h" -#include - -/* #define LOGF_ENABLE */ -#include "logf.h" - -static msc_drv* sd_to_msc[MSC_COUNT]; -static long _sd_last_disk_activity = 0; - -static void sd_probe_set_block_count(msc_drv* d) -{ - uint8_t scr[CACHEALIGN_SIZE] CACHEALIGN_ATTR; - msc_req req = {0}; - req.command = SD_SEND_SCR; - req.resptype = MSC_RESP_R1; - req.flags = MSC_RF_DATA; - req.data = scr; - req.nr_blocks = 1; - req.block_len = 8; - - if(msc_app_cmd_exec(d, &req) == MSC_REQ_SUCCESS) { - if(scr[3] & 0x02) - d->driver_flags |= MSC_DF_HAS_SBC; - else - d->driver_flags &= ~MSC_DF_HAS_SBC; - } -} - -static int sd_init_card(msc_drv* d) -{ - int s; - if(s = msc_cmd_go_idle_state(d)) return -100 - s; - if(s = msc_cmd_send_if_cond(d)) return -110 - s; - if(s = msc_cmd_app_op_cond(d)) return -120 - s; - if(s = msc_cmd_all_send_cid(d)) return -130 - s; - if(s = msc_cmd_send_rca(d)) return -140 - s; - if(s = msc_cmd_send_csd(d)) return -150 - s; - if(s = msc_cmd_select_card(d)) return -160 - s; - if(s = msc_cmd_set_clr_card_detect(d, 0)) return -170 - s; - if(s = msc_cmd_set_bus_width(d, 4)) return -180 - s; - if(s = msc_cmd_switch_freq(d)) return -190 - s; - sd_probe_set_block_count(d); - d->driver_flags |= MSC_DF_READY; - d->cardinfo.initialized = 1; - return 0; -} - -static int sd_transfer(msc_drv* d, bool write, - sector_t start, int count, void* buf) -{ - int status = -1; - - msc_lock(d); - if(!d->card_present) - goto _exit; - - /* Hopefully puts the driver into a working state */ - if(d->driver_flags & MSC_DF_ERRSTATE) { - logf("MSC%d: attempting to reset after ERRSTATE", d->msc_nr); - msc_full_reset(d); - } - - /* Init card if needed */ - if((d->driver_flags & MSC_DF_READY) == 0) { - if(status = sd_init_card(d)) { - logf("MSC%d: card init failed (code %d)", d->msc_nr, status); - d->driver_flags |= MSC_DF_ERRSTATE; - d->cardinfo.initialized = status; - goto _exit; - } - } - - /* Ensure parameters are within range */ - if(count < 1) - goto _exit; - if(start + count > d->cardinfo.numblocks) - goto _exit; - - do { - /* We can only do 65536 blocks at a time */ - int xfer_count = count > 0xffff ? 0xffff : count; - - /* Set block length. I think this is only necessary for non-HCS cards. - * HCS cards always use 512 bytes so we shouldn't need it. - */ - if((d->driver_flags & MSC_DF_HCS_CARD) == 0) - if(status = msc_cmd_set_block_len(d, SD_BLOCK_SIZE)) - goto _exit; - - bool use_sbc = false; - if(xfer_count > 1 && (d->driver_flags & MSC_DF_HAS_SBC)) { - msc_req sbc_req = {0}; - sbc_req.command = 23; /* CMD23: SET_BLOCK_COUNT (SBC) */ - sbc_req.argument = xfer_count; - sbc_req.resptype = MSC_RESP_R1; - status = msc_cmd_exec(d, &sbc_req); - if(status == MSC_REQ_SUCCESS) { - use_sbc = true; - } else if(status == MSC_REQ_CARD_ERR && - (sbc_req.response[0] & SD_R1_ILLEGAL_COMMAND)) { - d->driver_flags &= ~MSC_DF_HAS_SBC; - } else { - goto _exit; - } - } - - msc_req req = {0}; - req.data = buf; - req.nr_blocks = xfer_count; - req.block_len = SD_BLOCK_SIZE; - req.resptype = MSC_RESP_R1; - req.flags = MSC_RF_DATA; - if(xfer_count > 1) { - if(use_sbc) - req.flags |= MSC_RF_ERR_CMD12; - else - req.flags |= MSC_RF_AUTO_CMD12; - } - if(write) { - req.command = xfer_count == 1 ? SD_WRITE_BLOCK - : SD_WRITE_MULTIPLE_BLOCK; - req.flags |= MSC_RF_PROG | MSC_RF_WRITE; - } else { - req.command = xfer_count == 1 ? SD_READ_SINGLE_BLOCK - : SD_READ_MULTIPLE_BLOCK; - } - - // XXX 64-bit - if(d->driver_flags & MSC_DF_V2_CARD) - req.argument = start; - else - req.argument = start * SD_BLOCK_SIZE; - - if(status = msc_cmd_exec(d, &req)) - goto _exit; - - /* TODO - properly handle reading the last block of the SD card - * This is likely to fail if we're reading near the end because - * the SD card will try to read past the last sector and then - * signal an error. So we need to ignore that error, but only if - * it was expected to occur. (See SD spec sec. 4.3.3, "Block Read") - */ - if(status = msc_cmd_send_status(d)) - goto _exit; - - /* Advance the buffer and adjust start/count */ - buf += xfer_count * SD_BLOCK_SIZE; - start += xfer_count; - count -= xfer_count; - } while(count > 0); - - _exit: - msc_unlock(d); - return status; -} - -int sd_read_sectors(IF_MD(int drive,) sector_t start, - int count, void* buf) -{ - return sd_transfer(sd_to_msc[IF_MD_DRV(drive)], false, - start, count, buf); -} - -int sd_write_sectors(IF_MD(int drive,) sector_t start, - int count, const void* buf) -{ - return sd_transfer(sd_to_msc[IF_MD_DRV(drive)], true, - start, count, (void*)buf); -} - -tCardInfo* card_get_info_target(int card_nr) -{ - /* Defensive measures */ - if(card_nr < 0 || card_nr >= MSC_COUNT || sd_to_msc[card_nr] == NULL) { - static tCardInfo null_info = { 0 }; - return &null_info; - } - - return &sd_to_msc[card_nr]->cardinfo; -} - -int sd_event(long id, intptr_t data) -{ - if(id == SYS_HOTSWAP_EXTRACTED) { - msc_drv* d = msc_get_by_drive(data); - if(d) - msc_full_reset(d); - return 0; - } else { - return storage_event_default_handler(id, data, _sd_last_disk_activity, - STORAGE_SD); - } -} - -long sd_last_disk_activity(void) -{ - return _sd_last_disk_activity; -} - -bool sd_present(IF_MD_NONVOID(int drive)) -{ -#ifndef HAVE_MULTIDRIVE - int drive = 0; -#endif - /* Seems that volume_properties() in firmware/common/disk.c may pass - * drive = -1 when the SD card is not inserted, so just return false. - */ - if(drive < 0) - return false; - - return sd_to_msc[IF_MD_DRV(drive)]->card_present; -} - -bool sd_removable(IF_MD_NONVOID(int drive)) -{ -#ifndef HAVE_MULTIDRIVE - int drive = 0; -#endif - - /* Same reason as sd_present() */ - if(drive < 0) - return false; - - return sd_to_msc[IF_MD_DRV(drive)]->config->cd_gpio != GPIO_NONE; -} - -#ifndef CONFIG_STORAGE_MULTI -static -#endif -int sd_num_drives(int first_drive) -{ - int n = 0; - for(; n < MSC_COUNT; ++n) { - msc_drv* d = msc_get(MSC_TYPE_SD, n); - if(d == NULL) - break; - - d->drive_nr = first_drive + n; - sd_to_msc[n] = d; - } - - for(int i = n; i < MSC_COUNT; ++i) - sd_to_msc[i] = NULL; - - return n; -} - -int sd_init(void) -{ - msc_init(); -#ifndef CONFIG_STORAGE_MULTI - sd_num_drives(0); -#endif - - return 0; -} diff --git a/firmware/target/mips/ingenic_x1000/sdmmc-x1000-common.c b/firmware/target/mips/ingenic_x1000/sdmmc-x1000-common.c new file mode 100644 index 0000000000..9e3d4bc60e --- /dev/null +++ b/firmware/target/mips/ingenic_x1000/sdmmc-x1000-common.c @@ -0,0 +1,116 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2026 Aidan MacDonald + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#include "sdmmc_host.h" +#include "sdmmc_poll.h" +#include "msc-x1000.h" +#include "gpio-ingenic.h" +#include "irq-x1000.h" +#include "clk-x1000.h" + +/* All current X1000 targets use MSC0 */ +#define MSC_NUM 0 + +/* And all targets use an active low card dtect GPIO */ +#define MSC_CD_GPIO GPIO_MSC0_CD +#define MSC_CD_ACTIVE_LEVEL 0 + +/* Q1 uses MPLL, other targets are using SCLK_A */ +#if defined(SHANLING_Q1) +# define MSC_SOURCE_CLOCK X1000_CLK_MPLL +#else +# define MSC_SOURCE_CLOCK X1000_CLK_SCLK_A +#endif + +/* 300ms poll interval */ +#define SDCARD_POLL_TICKS (300 * HZ / 1000) + +static struct sdmmc_host sdmmc; +static struct sdmmc_poll sdmmc_poll; +static struct x1000_msc_controller msc_ctl; +static struct x1000_msc_dma_desc msc_dma_desc; + +static const struct sdmmc_controller_ops sdmmc_ops = { + .set_power_enabled = x1000_msc_set_power_enabled, + .set_bus_width = x1000_msc_set_bus_width, + .set_bus_clock = x1000_msc_set_bus_clock, + .submit_command = x1000_msc_submit_command, + .abort_command = x1000_msc_abort_command, +}; + +static const struct sdmmc_host_config sdmmc_config INITDATA_ATTR = { + .type = STORAGE_SD, + .bus_voltages = SDMMC_BUS_VOLTAGE_3V2_3V3 | + SDMMC_BUS_VOLTAGE_3V3_3V4, + .bus_widths = SDMMC_BUS_WIDTH_1BIT | + SDMMC_BUS_WIDTH_4BIT, + .bus_clocks = SDMMC_BUS_CLOCK_400KHZ | + SDMMC_BUS_CLOCK_25MHZ | + SDMMC_BUS_CLOCK_50MHZ, + .max_nr_blocks = 65535, + .is_removable = true, +}; + +static bool is_sdcard_inserted(void) +{ + return gpio_get_level(MSC_CD_GPIO) == MSC_CD_ACTIVE_LEVEL; +} + +static void sdcard_insert_irq(void) +{ + sdmmc_poll_event(&sdmmc_poll); + gpio_flip_edge_irq(MSC_CD_GPIO); +} + +void sdmmc_host_target_init(void) +{ + /* Configure clock source */ + jz_writef(CPM_MSC0CDR, CE(1), + CLKSRC(MSC_SOURCE_CLOCK == X1000_CLK_MPLL ? 1 : 0)); + while (jz_readf(CPM_MSC0CDR, BUSY)); + jz_writef(CPM_MSC0CDR, CE(0)); + + /* Initialize controller */ + x1000_msc_init(&msc_ctl, &msc_dma_desc, MSC_NUM, clk_get(MSC_SOURCE_CLOCK)); + system_enable_irq(MSC_NUM == 0 ? IRQ_MSC0 : IRQ_MSC1); + + /* Initialize SD/MMC host driver */ + sdmmc_host_init(&sdmmc, &sdmmc_config, &sdmmc_ops, &msc_ctl); + sdmmc_host_init_medium_present(&sdmmc, is_sdcard_inserted()); + + /* Setup card detect handling */ + sdmmc_poll_init(&sdmmc_poll, &sdmmc, is_sdcard_inserted); + + system_set_irq_handler(GPIO_TO_IRQ(MSC_CD_GPIO), sdcard_insert_irq); + gpio_set_function(MSC_CD_GPIO, GPIOF_IRQ_EDGE(1)); + gpio_flip_edge_irq(MSC_CD_GPIO); + gpio_enable_irq(MSC_CD_GPIO); +} + +#if MSC_NUM == 0 +void MSC0(void) +{ + x1000_msc_irq_handler(&msc_ctl); +} +#else +void MSC1(void) +{ + x1000_msc_irq_handler(&msc_ctl); +} +#endif diff --git a/firmware/target/mips/ingenic_x1000/shanlingq1/audiohw-shanlingq1.c b/firmware/target/mips/ingenic_x1000/shanlingq1/audiohw-shanlingq1.c index b55208c05f..518f5d9f3f 100644 --- a/firmware/target/mips/ingenic_x1000/shanlingq1/audiohw-shanlingq1.c +++ b/firmware/target/mips/ingenic_x1000/shanlingq1/audiohw-shanlingq1.c @@ -22,8 +22,8 @@ #include "system.h" #include "pcm_sampr.h" #include "aic-x1000.h" -#include "i2c-x1000.h" -#include "gpio-x1000.h" +#include "i2c-ingenic.h" +#include "gpio-ingenic.h" #include "x1000/aic.h" #include "x1000/cpm.h" diff --git a/firmware/target/mips/ingenic_x1000/shanlingq1/button-shanlingq1.c b/firmware/target/mips/ingenic_x1000/shanlingq1/button-shanlingq1.c index 66806bc013..1b06481c01 100644 --- a/firmware/target/mips/ingenic_x1000/shanlingq1/button-shanlingq1.c +++ b/firmware/target/mips/ingenic_x1000/shanlingq1/button-shanlingq1.c @@ -26,9 +26,9 @@ #include "kernel.h" #include "backlight.h" #include "powermgmt.h" -#include "gpio-x1000.h" +#include "gpio-ingenic.h" #include "irq-x1000.h" -#include "i2c-x1000.h" +#include "i2c-ingenic.h" #include #ifndef BOOTLOADER diff --git a/firmware/target/mips/ingenic_x1000/shanlingq1/lcd-shanlingq1.c b/firmware/target/mips/ingenic_x1000/shanlingq1/lcd-shanlingq1.c index 4cb72ceef0..f2830919d1 100644 --- a/firmware/target/mips/ingenic_x1000/shanlingq1/lcd-shanlingq1.c +++ b/firmware/target/mips/ingenic_x1000/shanlingq1/lcd-shanlingq1.c @@ -21,7 +21,7 @@ #include "lcd.h" #include "system.h" #include "lcd-x1000.h" -#include "gpio-x1000.h" +#include "gpio-ingenic.h" /* LCD controller is probably an RM68090. */ diff --git a/firmware/target/mips/ingenic_x1000/shanlingq1/power-shanlingq1.c b/firmware/target/mips/ingenic_x1000/shanlingq1/power-shanlingq1.c index 1a3a2bc164..4efd57857b 100644 --- a/firmware/target/mips/ingenic_x1000/shanlingq1/power-shanlingq1.c +++ b/firmware/target/mips/ingenic_x1000/shanlingq1/power-shanlingq1.c @@ -29,7 +29,7 @@ # include "usb_core.h" #endif -#include "i2c-x1000.h" +#include "i2c-ingenic.h" /* TODO: Better(?) battery reporting for Q1 using CW2015 driver * diff --git a/firmware/target/mips/ingenic_x1000/spl-x1000.c b/firmware/target/mips/ingenic_x1000/spl-x1000.c index fd8a49e14d..4461be33b9 100644 --- a/firmware/target/mips/ingenic_x1000/spl-x1000.c +++ b/firmware/target/mips/ingenic_x1000/spl-x1000.c @@ -21,8 +21,8 @@ #include "system.h" #include "spl-x1000.h" #include "clk-x1000.h" -#include "nand-x1000.h" -#include "gpio-x1000.h" +#include "nand-ingenic.h" +#include "gpio-ingenic.h" #include "boot-x1000.h" #include "x1000/cpm.h" #include "x1000/ost.h" diff --git a/firmware/target/mips/ingenic_x1000/system-x1000.c b/firmware/target/mips/ingenic_x1000/system-x1000.c index adc6ecf924..36f2a9fc9a 100644 --- a/firmware/target/mips/ingenic_x1000/system-x1000.c +++ b/firmware/target/mips/ingenic_x1000/system-x1000.c @@ -23,8 +23,8 @@ #include "mips.h" #include "panic.h" #include "button.h" -#include "gpio-x1000.h" -#include "dma-x1000.h" +#include "gpio-ingenic.h" +#include "dma-ingenic.h" #include "irq-x1000.h" #include "clk-x1000.h" #include "boot-x1000.h" @@ -196,13 +196,6 @@ void system_reboot(void) while(1); } -int system_memory_guard(int mode) -{ - /* unused */ - (void)mode; - return 0; -} - /* Simple delay API -- slow path functions */ void __udelay(uint32_t us) diff --git a/firmware/target/mips/ingenic_x1000/uart-x1000.c b/firmware/target/mips/ingenic_x1000/uart-x1000.c index fd8ea9e4b7..e298bce8a6 100644 --- a/firmware/target/mips/ingenic_x1000/uart-x1000.c +++ b/firmware/target/mips/ingenic_x1000/uart-x1000.c @@ -18,7 +18,7 @@ * ****************************************************************************/ -#include "gpio-x1000.h" +#include "gpio-ingenic.h" #include "irq-x1000.h" #include "kernel.h" #include "panic.h" diff --git a/firmware/target/mips/ingenic_x1000/usb-x1000.c b/firmware/target/mips/ingenic_x1000/usb-x1000.c index 2c82b47cd5..d933813bf0 100644 --- a/firmware/target/mips/ingenic_x1000/usb-x1000.c +++ b/firmware/target/mips/ingenic_x1000/usb-x1000.c @@ -24,7 +24,7 @@ #include "usb_drv.h" #include "usb-designware.h" #include "irq-x1000.h" -#include "gpio-x1000.h" +#include "gpio-ingenic.h" #include "x1000/cpm.h" /* diff --git a/firmware/usb.c b/firmware/usb.c index 1e91efd96b..b5b556516c 100644 --- a/firmware/usb.c +++ b/firmware/usb.c @@ -50,7 +50,7 @@ #include "gui/skin_engine/skin_engine.h" #endif -#if defined(HIBY_R3PROII) || defined(HIBY_R1) +#if defined(HIBY_R3PROII) || defined(HIBY_R1) || defined(HIDIZS_AP80MAX) #include "usb-hiby-gadget.h" #endif #if defined(DX50) || defined(DX90) @@ -91,7 +91,7 @@ void usb_set_mode(int mode) usb_mode = mode; #if defined(DX50) || defined(DX90) ibasso_set_usb_mode(mode); -#elif defined(HIBY_R3PROII) || defined(HIBY_R1) +#elif defined(HIBY_R3PROII) || defined(HIBY_R1) || defined(HIDIZS_AP80MAX) hiby_set_usb_mode(mode); #endif } @@ -117,6 +117,9 @@ static struct event_queue usb_queue SHAREDBSS_ATTR; #ifdef USB_ENABLE_HID static bool usb_hid = true; #endif +#ifdef USB_ENABLE_SERIAL +static bool usb_serial = false; +#endif #ifdef USB_ENABLE_AUDIO static int usb_audio = 0; #endif @@ -215,6 +218,9 @@ static inline void usb_configure_drivers(int for_state) usb_core_enable_driver(USB_DRIVER_HID, true); #endif /* USB_ENABLE_CHARGING_ONLY */ #endif /* USB_ENABLE_HID */ +#ifdef USB_ENABLE_SERIAL + usb_core_enable_driver(USB_DRIVER_SERIAL, usb_serial); +#endif #ifdef USB_ENABLE_AUDIO usb_core_enable_driver(USB_DRIVER_AUDIO, (usb_audio == 1) || (usb_audio == 2)); // while "always" or "only in charge-only mode" #endif /* USB_ENABLE_AUDIO */ @@ -237,6 +243,9 @@ static inline void usb_configure_drivers(int for_state) #ifdef USB_ENABLE_HID usb_core_enable_driver(USB_DRIVER_HID, usb_hid); #endif +#ifdef USB_ENABLE_SERIAL + usb_core_enable_driver(USB_DRIVER_SERIAL, usb_serial); +#endif #ifdef USB_ENABLE_AUDIO usb_core_enable_driver(USB_DRIVER_AUDIO, (usb_audio == 1) || (usb_audio == 3)); // while "always" or "only in mass-storage mode" #endif /* USB_ENABLE_AUDIO */ @@ -422,6 +431,15 @@ static void NORETURN_ATTR usb_thread(void) #ifdef HAVE_USBSTACK case USB_NOTIFY_SET_ADDR: +#ifdef USB_DETECT_BY_REQUEST + /* first sign of a host that addresses before anything else; + * the class drivers must be on before interfaces are assigned */ + if(usb_state > USB_EXTRACTED) { + usb_state = USB_INSERTED; + usb_set_host_present(true); + } +#endif + /* intentional fallthrough */ case USB_NOTIFY_SET_CONFIG: case USB_NOTIFY_BUS_RESET: case USB_NOTIFY_CLASS_DRIVER: @@ -495,6 +513,9 @@ static void NORETURN_ATTR usb_thread(void) if(usb_host_present && exclusive_storage_requested) { usb_slave_mode(true); exclusive_storage_enabled = true; +#ifdef USB_ENABLE_STORAGE + usb_signal_class_notify(USB_DRIVER_MASS_STORAGE, 0); +#endif } break; /* SYS_USB_CONNECTED_ACK */ @@ -853,10 +874,20 @@ bool usb_exclusive_storage(void) void usb_request_exclusive_storage(void) { + if(exclusive_storage_requested) + return; + exclusive_storage_requested = true; usb_broadcast_seqnum += 1; usb_num_acks_to_expect = queue_broadcast(SYS_USB_CONNECTED, usb_broadcast_seqnum) - 1; DEBUGF("usb: waiting for %d acks...\n", usb_num_acks_to_expect); + if(usb_num_acks_to_expect == 0 && usb_host_present) { + usb_slave_mode(true); + exclusive_storage_enabled = true; +#ifdef USB_ENABLE_STORAGE + usb_signal_class_notify(USB_DRIVER_MASS_STORAGE, 0); +#endif + } } void usb_release_exclusive_storage(void) @@ -866,6 +897,7 @@ void usb_release_exclusive_storage(void) return; } exclusive_storage_requested = false; + usb_num_acks_to_expect = 0; if(exclusive_storage_enabled) { usb_slave_mode(false); @@ -890,6 +922,19 @@ void usb_set_hid(bool enable) } #endif /* USB_ENABLE_HID */ +#ifdef USB_ENABLE_SERIAL +void usb_set_serial(bool enable) +{ + usb_serial = enable; + usb_core_enable_driver(USB_DRIVER_SERIAL, usb_serial); +} + +bool usb_get_serial(void) +{ + return usb_serial; +} +#endif /* USB_ENABLE_SERIAL */ + #ifdef USB_ENABLE_AUDIO void usb_set_audio(int value) { diff --git a/firmware/usbstack/iap/audio.c b/firmware/usbstack/iap/audio.c index 9dcbd01025..32cd196c82 100644 --- a/firmware/usbstack/iap/audio.c +++ b/firmware/usbstack/iap/audio.c @@ -33,6 +33,7 @@ static const unsigned long samprs[] = { SAMPR_48, SAMPR_44, + SAMPR_32, }; struct StagingBuffer { @@ -83,6 +84,9 @@ static size_t calc_packet_size(uint8_t cur_sampr, uint8_t packet_count) { if(cur_sampr == 0) { /*48k*/ return 192; + } else if(cur_sampr == 2) { + /*32k*/ + return 128; } else { /*44.1k*/ return packet_count % 10 == 0 ? 180 : 176; @@ -100,7 +104,7 @@ start: if(exhausted || cur_freq != set_freq) { *ptr = zero_buffer.buf.ptr; *len = packet_size; - packet_count += 1; + packet_count = (packet_count + 1) % 10; return; } @@ -134,7 +138,7 @@ start: *len = stage->cursor; stage->cursor = 0; staging_buffer_index = (staging_buffer_index + 1) % USB_BATCH_SLOTS; - packet_count += 1; + packet_count = (packet_count + 1) % 10; #if AUDIO_STAT == 1 sample += packet_size / 4; if(current_tick >= last_hz + HZ) { @@ -187,6 +191,7 @@ struct pcm_sink iap_pcm_sink = { .samprs = samprs, .num_samprs = ARRAYLEN(samprs), .default_freq = 0, + .volume_type = PCM_SINK_HWVOL, }, .ops = { .init = sink_nop, diff --git a/firmware/usbstack/iap/libiap/iap.c b/firmware/usbstack/iap/libiap/iap.c index b5f700b0a2..af1fe2545c 100644 --- a/firmware/usbstack/iap/libiap/iap.c +++ b/firmware/usbstack/iap/libiap/iap.c @@ -1,6 +1,8 @@ #include #include +#include "misc.h" + #include "constants.h" #include "endian.h" #include "iap.h" @@ -201,6 +203,25 @@ static int32_t handle_command(struct IAPContext* ctx, uint8_t lingo, uint16_t co response->is_ext_mode = 1; return IAPGeneralCommandID_ReturnExtendedInterfaceMode; } break; + case IAPGeneralCommandID_RequestIPodName: { + // Note: This should be pulled from .rockbox/playername.txt + char ipod_name[32] = "Rockbox"; + int fd = open_utf8(ROCKBOX_DIR "/playername.txt", O_RDONLY); + if (fd >= 0) { + read_line(fd, ipod_name, sizeof(ipod_name)); + close(fd); + ipod_name[sizeof(ipod_name)-1] = 0; + } + + check_ret( + iap_span_append(response_span, + ipod_name, + strlen(ipod_name) + 1), + -IAPAckStatus_EOutOfResource + ); + + return IAPGeneralCommandID_ReturnIPodName; + } break; case IAPGeneralCommandID_RequestIPodSoftwareVersion: { alloc_response(IAPReturnIPodSoftwareVersionPayload); response->major = 18; diff --git a/firmware/usbstack/usb_core.c b/firmware/usbstack/usb_core.c index b0b32e85ea..f1b7359973 100644 --- a/firmware/usbstack/usb_core.c +++ b/firmware/usbstack/usb_core.c @@ -58,6 +58,8 @@ /* include order matters, include driver header before usb_drv.h */ #if CONFIG_USBOTG == USBOTG_DESIGNWARE #include "usb-designware.h" +#elif CONFIG_USBOTG == USBOTG_RK27XX +#include "usb-rk27xx.h" #endif #include "usb_drv.h" @@ -225,6 +227,7 @@ enum { EP0_EXPECT_RX_STATUS_COMP, /* sending status */ }; static volatile int ep0_state; +static bool control_cancelled; #define TRACE_EP0_STATE 0 #if TRACE_EP0_STATE == 1 @@ -245,17 +248,11 @@ struct usb_transfer_completion_event_data int length; }; -typedef void (*completion_handler_t)(int ep, int dir, int status, int length); -typedef bool (*fast_completion_handler_t)(int ep, int dir, int status, int length); -typedef bool (*control_handler_t)(struct usb_ctrlrequest* req, uint8_t* reqdata, size_t reqdata_size); - static struct { - completion_handler_t completion_handler[2]; - fast_completion_handler_t fast_completion_handler[2]; - control_handler_t control_handler[2]; - struct usb_transfer_completion_event_data completion_event[2]; -} ep_data[USB_NUM_ENDPOINTS]; + struct usb_class_driver* driver; + struct usb_transfer_completion_event_data completion_event; +} ep_data[USB_NUM_ENDPOINTS][2]; struct ep_alloc_state { int8_t type[2]; @@ -506,9 +503,9 @@ void usb_core_handle_transfer_completion( return; } - completion_handler_t handler = ep_data[num].completion_handler[dir]; - if(handler != NULL) - handler(num, dir == DIR_IN ? USB_DIR_IN : USB_DIR_OUT, event->status, event->length); + struct usb_class_driver* driver = ep_data[num][dir].driver; + if(driver && driver->transfer_complete) + driver->transfer_complete(num, dir == DIR_IN ? USB_DIR_IN : USB_DIR_OUT, event->status, event->length); } void usb_core_enable_driver(int driver, bool enabled) @@ -613,9 +610,7 @@ static void init_deinit_endpoints(int config, bool init) { int ep = epnum | (dir == DIR_OUT ? USB_DIR_OUT : USB_DIR_IN); if(init) { usb_drv_ep_init(&cstate->ep_alloc_ctx, ep); - ep_data[epnum].completion_handler[dir] = driver->transfer_complete; - ep_data[epnum].fast_completion_handler[dir] = driver->fast_transfer_complete; - ep_data[epnum].control_handler[dir] = driver->control_request; + ep_data[epnum][dir].driver = driver; } else { usb_drv_ep_deinit(&cstate->ep_alloc_ctx, ep); } @@ -787,7 +782,7 @@ static void request_handler_device_get_descriptor(struct usb_ctrlrequest* req, u case USB_DT_OTHER_SPEED_CONFIG: case USB_DT_CONFIG: { - if(index > NUM_CONFIGS) { + if(index >= NUM_CONFIGS) { logf("invalid config dt index %u", index); break; } @@ -814,7 +809,7 @@ static void request_handler_device_get_descriptor(struct usb_ctrlrequest* req, u size = sizeof(struct usb_config_descriptor); for(i = 0; i < USB_NUM_DRIVERS; i++) { - if(drivers[i]->enabled && drivers[i]->config == index + 1 && drivers[i]->get_config_descriptor) { + if(drivers[i]->enabled && !drivers[i]->error && drivers[i]->config == index + 1 && drivers[i]->get_config_descriptor) { size += drivers[i]->get_config_descriptor(reqdata + size, max_packet_size); } } @@ -870,9 +865,22 @@ static void request_handler_device_get_descriptor(struct usb_ctrlrequest* req, u } } -static void usb_core_do_set_addr(uint8_t address) +static void usb_core_init_interfaces(void) +{ + if(usb_state == DEFAULT) { + set_serial_descriptor(); + usb_core_set_serial_function_id(); + allocate_interfaces_and_endpoints(); + } +} + +static void usb_core_set_address(uint8_t address) { logf("usb_core: SET_ADR %d", address); + /* A host may address the device before making any other request; + * the driver answers SET_ADDRESS itself, so nothing has been + * allocated yet. */ + usb_core_init_interfaces(); usb_address = address; usb_state = ADDRESS; } @@ -930,13 +938,18 @@ static int usb_core_do_set_config(uint8_t new_config) } if(require_exclusive) { - if(!usb_exclusive_storage()) { - usb_release_exclusive_storage(); - usb_request_exclusive_storage(); - } - } else { + /* Also preserve a pending handover across repeated SET_CONFIG. + * Restarting it discards acknowledgements and races the USB UI. */ + usb_request_exclusive_storage(); + } else if(!bus_reset_pending) { usb_release_exclusive_storage(); } + /* A bus reset is not a physical disconnect. Keep the storage handover + * (and its acknowledgement epoch) until the host selects a new config + * or the cable is removed; remounting here races re-enumeration. + * If the host never sends SET_CONFIGURATION again, local storage stays + * unavailable until unplug. We expect reconfiguration promptly after a + * reset, but deliberately do not remount on a timer. */ if(require_cpu_boost) { trigger_cpu_boost(); thread_set_priority(thread_self(), PRIORITY_REALTIME); @@ -945,6 +958,7 @@ static int usb_core_do_set_config(uint8_t new_config) cancel_cpu_boost(); } + #ifdef HAVE_USB_CHARGING_ENABLE usb_charging_maxcurrent_change(usb_charging_maxcurrent()); #endif @@ -964,8 +978,6 @@ static void usb_core_do_clear_feature(int recip, int recip_nr, int feature) static void request_handler_device(struct usb_ctrlrequest* req, uint8_t* reqdata, size_t reqdata_size) { - unsigned address; - switch(req->bRequest) { case USB_REQ_GET_CONFIGURATION: logf("usb_core: GET_CONFIG"); @@ -980,16 +992,6 @@ static void request_handler_device(struct usb_ctrlrequest* req, uint8_t* reqdata usb_core_control_response(USB_CONTROL_STALL, NULL, 0); } break; - case USB_REQ_SET_ADDRESS: - /* NOTE: We really have no business handling this and drivers - * should just handle it themselves. We don't care beyond - * knowing if we've been assigned an address yet, or not. */ - address = req->wValue; - usb_drv_cancel_all_transfers(); - usb_core_control_response(USB_CONTROL_ACK, NULL, 0); - usb_drv_set_address(address); - usb_core_do_set_addr(address); - break; case USB_REQ_GET_DESCRIPTOR: logf("usb_core: GET_DESC %d", req->wValue >> 8); request_handler_device_get_descriptor(req, reqdata, reqdata_size); @@ -1065,22 +1067,27 @@ static void request_handler_interface(struct usb_ctrlrequest* req, uint8_t* reqd static void request_handler_endpoint_drivers(struct usb_ctrlrequest* req, uint8_t* reqdata, size_t reqdata_size) { - bool handled = false; - control_handler_t control_handler = NULL; + int num = EP_NUM(req->wIndex); + int dir = EP_DIR(req->wIndex); - if(EP_NUM(req->wIndex) < USB_NUM_ENDPOINTS) - control_handler = - ep_data[EP_NUM(req->wIndex)].control_handler[EP_DIR(req->wIndex)]; + if(num >= USB_NUM_ENDPOINTS) + goto error; - if(control_handler) - handled = control_handler(req, reqdata, reqdata_size); + struct usb_class_driver* driver = ep_data[num][dir].driver; - if(!handled) { + if(!driver || !driver->control_request) + goto error; + + if(!driver->control_request(req, reqdata, reqdata_size)) + goto error; + + return; + +error: /* nope. flag error */ logf("bad req 0x%x:0x%x:0x%x:0x%x:0x%x", req->bRequestType,req->bRequest, req->wValue, req->wIndex, req->wLength); usb_core_control_response(USB_CONTROL_STALL, NULL, 0); - } } static void request_handler_endpoint_standard(struct usb_ctrlrequest* req, uint8_t* reqdata, size_t reqdata_size) @@ -1142,12 +1149,7 @@ static void usb_core_control_request_handler(struct usb_ctrlrequest* req, uint8_ usb_charging_maxcurrent_change(usb_charging_maxcurrent()); } #endif - if(usb_state == DEFAULT) { - set_serial_descriptor(); - usb_core_set_serial_function_id(); - - allocate_interfaces_and_endpoints(); - } + usb_core_init_interfaces(); switch(req->bRequestType & USB_RECIP_MASK) { case USB_RECIP_DEVICE: @@ -1192,7 +1194,7 @@ void usb_core_bus_reset(void) static void signal_xfer_complete(int ep, struct usb_ctrlrequest* req, int status, int length) { struct usb_transfer_completion_event_data* completion_event = - &ep_data[EP_NUM(ep)].completion_event[EP_DIR(ep)]; + &ep_data[EP_NUM(ep)][EP_DIR(ep)].completion_event; completion_event->req = req; completion_event->ep = ep; @@ -1202,6 +1204,7 @@ static void signal_xfer_complete(int ep, struct usb_ctrlrequest* req, int status } static void process_setup_request(struct usb_ctrlrequest* req) { + control_cancelled = false; set_ep0_state(req->bRequestType & USB_DIR_IN ? EP0_HANDLING_TX_CONTROL : EP0_HANDLING_RX_CONTROL); if(ep0_state == EP0_HANDLING_TX_CONTROL || req->wLength == 0) { @@ -1213,6 +1216,7 @@ static void process_setup_request(struct usb_ctrlrequest* req) { if(req->wLength > sizeof(usb_control_data)) { logf("usb_core: control write too large %u > %u", req->wLength, sizeof(usb_control_data)); usb_drv_stall(EP_CONTROL, true, false); + set_ep0_state(EP0_READY); return; } set_ep0_state(EP0_EXPECT_RX_DATA_COMP); @@ -1234,30 +1238,30 @@ static bool check_for_new_setup(void) { } /* called by usb_drv_transfer_completed() */ -void usb_core_transfer_complete(int endpoint, int dir, int status, int length) { +void usb_core_transfer_complete(int ep, int dir, int status, int length) { #ifdef USB_BATCH_NON_NATIVE /* batch api */ - if(batch_ep != 0 && (endpoint | dir) == batch_ep) { + if(batch_ep != 0 && (ep | dir) == batch_ep) { batch_xfer_complete(); return; } #endif /* Fast notification */ - fast_completion_handler_t handler = ep_data[endpoint].fast_completion_handler[EP_DIR(dir)]; - if(handler != NULL && handler(endpoint, dir, status, length)) { + struct usb_class_driver* driver = ep_data[ep][EP_DIR(dir)].driver; + if(driver && driver->fast_transfer_complete && driver->fast_transfer_complete(ep, dir, status, length)) { return; /* do not dispatch to the queue if handled */ } /* Non-control packet handling */ - if(endpoint != EP_CONTROL) { - signal_xfer_complete(endpoint | dir, NULL, status, length); + if(ep != EP_CONTROL) { + signal_xfer_complete(ep | dir, NULL, status, length); return; } /* Control packet handling */ switch(dir | ep0_state) { - /* EXPECT_TX_DATA_STATUS_COMP -(status comp)-> EXPECT_TX_DATA_COMP -(data comp)-> READY + /* EXPECT_TX_DATA_STATUS_COMP -(status comp)-> EXPECT_TX_DATA_COMP -(data comp)-> READY * -(data comp)-> EXPECT_TX_STATUS_COMP -(status comp)-> READY */ case USB_DIR_OUT | EP0_EXPECT_TX_DATA_STATUS_COMP: logf("usb_core: control-in done success=%d", status == 0 && length == 0); @@ -1298,7 +1302,7 @@ void usb_core_handle_notify(long id, intptr_t data) switch(id) { case USB_NOTIFY_SET_ADDR: - usb_core_do_set_addr(data); + usb_core_set_address(data); break; case USB_NOTIFY_SET_CONFIG: usb_core_do_set_config(data); @@ -1329,17 +1333,32 @@ void usb_core_handle_notify(long id, intptr_t data) } } +void usb_core_control_cancelled(void) +{ + /* A newer SETUP supersedes even a request deferred behind a handler. */ + have_pending_request = false; + /* A queued/running handler still owns handling_request and the shared + * buffer. Suppress its response; a later SETUP may replace it on return. */ + if(ep0_state == EP0_HANDLING_TX_CONTROL || ep0_state == EP0_HANDLING_RX_CONTROL) + control_cancelled = true; + else + set_ep0_state(EP0_READY); +} + void usb_core_setup_received(struct usb_ctrlrequest* req) { if(bus_reset_pending) { logf("usb_core: bus resetting tick=%lu", current_tick); return; } + /* Drivers cancel abandoned EP0 transfers before delivering replacement + * SETUP. A handler may still own the request buffer; defer until it returns. */ if(ep0_state != EP0_READY) { logf("usb_core: control pending tick=%lu", current_tick); pending_request = *req; have_pending_request = true; return; } + have_pending_request = false; handling_request = *req; process_setup_request(&handling_request); } @@ -1347,13 +1366,27 @@ void usb_core_setup_received(struct usb_ctrlrequest* req) { void usb_core_control_response(enum usb_control_response response, const void* data, size_t size) { logf("usb_core: response ack=%d size=%u ep0_state=%d tick=%lu", response, size, ep0_state, current_tick); + int oldlevel = disable_irq_save(); + if(bus_reset_pending) { + restore_irq(oldlevel); + return; + } if((ep0_state == EP0_HANDLING_TX_CONTROL || ep0_state == EP0_HANDLING_RX_CONTROL) && check_for_new_setup()) { + restore_irq(oldlevel); + return; + } + + if(control_cancelled) { + control_cancelled = false; + set_ep0_state(EP0_READY); + restore_irq(oldlevel); return; } if(response == USB_CONTROL_STALL) { set_ep0_state(EP0_READY); usb_drv_stall(EP_CONTROL, true, true); + restore_irq(oldlevel); return; } @@ -1390,6 +1423,7 @@ void usb_core_control_response(enum usb_control_response response, const void* d panicf("usb_core: invalid control response ep_state=%d", ep0_state); break; } + restore_irq(oldlevel); } void usb_core_notify_set_address(uint8_t addr) diff --git a/firmware/usbstack/usb_iap.c b/firmware/usbstack/usb_iap.c index 6b2cfd6fe9..3c63527744 100644 --- a/firmware/usbstack/usb_iap.c +++ b/firmware/usbstack/usb_iap.c @@ -119,23 +119,16 @@ static struct usb_as_interface ipod_audio_stream_1_uac_header = { .wFormatTag = USB_AS_FORMAT_TYPE_I_PCM, }; -/* TODO: remove unsupported freqs */ static struct usb_as_format_type_i_discrete ipod_audio_stream_1_uac_discrete = { - .bLength = USB_AS_SIZEOF_FORMAT_TYPE_I_DISCRETE(9), + .bLength = USB_AS_SIZEOF_FORMAT_TYPE_I_DISCRETE(3), .bDescriptorType = USB_DT_CS_INTERFACE, .bDescriptorSubType = USB_AS_FORMAT_TYPE, .bFormatType = USB_AS_FORMAT_TYPE_I, .bNrChannels = 2, .bSubframeSize = 2, /* bBitResolution / 8 */ .bBitResolution = 16, - .bSamFreqType = 9, + .bSamFreqType = 3, .tSamFreq = { - {0x40, 0x1F, 0x00}, /* 8000 */ - {0x11, 0x2B, 0x00}, /* 11025 */ - {0xE0, 0x2E, 0x00}, /* 12000 */ - {0x80, 0x3E, 0x00}, /* 16000 */ - {0x22, 0x56, 0x00}, /* 22050 */ - {0xC0, 0x5D, 0x00}, /* 24000 */ {0x00, 0x7D, 0x00}, /* 32000 */ {0x44, 0xAC, 0x00}, /* 44100 */ {0x80, 0xBB, 0x00}, /* 48000 */ @@ -339,7 +332,8 @@ static int usb_iap_get_config_descriptor(unsigned char* dest, int max_packet_siz PACK_DESC(ipod_audio_control_desc); ipod_audio_control_uac_header.baInterfaceNr[0] = stream.interface; ipod_audio_control_uac_header.wTotalLength = - sizeof(ipod_audio_control_uac_header) + + /* sizeof omits the flexible baInterfaceNr array. */ + ipod_audio_control_uac_header.bLength + sizeof(ipod_audio_control_uac_input_terminal) + sizeof(ipod_audio_control_uac_output_terminal); PACK_DESC(ipod_audio_control_uac_header); @@ -375,6 +369,7 @@ static int usb_iap_get_config_descriptor(unsigned char* dest, int max_packet_siz static int usb_iap_init_connection(void) { stream.sample_rate = 48000; + stream.alt = 0; last_charge_state = -1; last_minute = -1; last_hold_switch_state = -1; @@ -424,6 +419,8 @@ cleanup_audio: static int usb_iap_set_interface(int intf, int alt) { LOG("set interface interface=%d alt=%d", intf, alt); + if(intf == ctrl.interface || intf == hid.interface) + return alt == 0 ? 0 : -1; check_act(intf == stream.interface, return -1); if(alt == 0) { check_act(iap_audio_disable(), return -1); @@ -433,11 +430,14 @@ static int usb_iap_set_interface(int intf, int alt) { ERROR("invalid alt %d", alt); return -1; } + stream.alt = alt; return 0; } static int usb_iap_get_interface(int intf) { LOG("get interface interface=%d", intf); + if(intf == ctrl.interface || intf == hid.interface) + return 0; check_act(intf == stream.interface, return -1); return stream.alt; } @@ -572,9 +572,10 @@ static bool control_request_if_endpoint(struct usb_ctrlrequest* req, uint8_t* re switch(control_selector) { case USB_AS_EP_CS_SAMPLING_FREQ_CTL: check_act(req->wLength == 3, goto stall); - stream.sample_rate = reqdata[0] | (reqdata[1] << 8) | (reqdata[2] << 16); + uint32_t sample_rate = reqdata[0] | (reqdata[1] << 8) | (reqdata[2] << 16); + check_act(iap_audio_set_sampr(sample_rate), goto stall); + stream.sample_rate = sample_rate; LOG("audio stream sampling rate %lu", stream.sample_rate); - check_act(iap_audio_set_sampr(stream.sample_rate), goto stall); break; } usb_core_control_response(USB_CONTROL_ACK, NULL, 0); diff --git a/firmware/usbstack/usb_serial.c b/firmware/usbstack/usb_serial.c index 4820d66d62..a9c06ff34d 100644 --- a/firmware/usbstack/usb_serial.c +++ b/firmware/usbstack/usb_serial.c @@ -288,6 +288,7 @@ static bool usb_serial_control_request(struct usb_ctrlrequest* req, uint8_t* req { /* Receive line coding into local copy */ memcpy(line_coding.raw, reqdata, sizeof(struct cdc_line_coding)); + usb_core_control_response(USB_CONTROL_ACK, NULL, 0); handled = true; } } diff --git a/firmware/usbstack/usb_storage.c b/firmware/usbstack/usb_storage.c index 7f9057f643..a262770e4a 100644 --- a/firmware/usbstack/usb_storage.c +++ b/firmware/usbstack/usb_storage.c @@ -127,6 +127,7 @@ #define SENSE_MEDIUM_ERROR 0x03 #define SENSE_ILLEGAL_REQUEST 0x05 #define SENSE_UNIT_ATTENTION 0x06 +#define SENSE_DATA_PROTECT 0x07 #define ASC_MEDIUM_NOT_PRESENT 0x3a #define ASC_INVALID_FIELD_IN_CBD 0x24 @@ -135,6 +136,14 @@ #define ASC_READ_ERROR 0x11 #define ASC_NOT_READY 0x04 #define ASC_INVALID_COMMAND 0x20 +#define ASC_WRITE_PROTECTED 0x27 + +/* A drive whose driver can only read it is reported write protected */ +#ifdef HAVE_STORAGE_READONLY +#define LUN_READONLY(lun) storage_readonly(lun) +#else +#define LUN_READONLY(lun) false +#endif #define ASCQ_BECOMING_READY 0x01 @@ -296,6 +305,7 @@ static struct { unsigned char *data[2]; unsigned char data_select; unsigned int last_result; + unsigned int data_residue; } cur_cmd; static struct { @@ -307,6 +317,8 @@ static struct { static void handle_scsi(struct command_block_wrapper* cbw); static void send_csw(int status); +static void reject_command(const struct command_block_wrapper *cbw); +static void account_data_transfer(int dir, int status, int length); static void send_command_result(void *data,int size); static void send_command_failed_result(void); static void send_block_data(void *data,int size); @@ -339,6 +351,7 @@ static unsigned char* ramdisk_buffer; static enum { WAITING_FOR_COMMAND, + WAITING_FOR_STORAGE, SENDING_BLOCKS, SENDING_RESULT, SENDING_FAILED_RESULT, @@ -435,9 +448,14 @@ static int usb_storage_get_config_descriptor(unsigned char *dest,int max_packet_ #else static int usb_handle = 0; #endif -static int usb_storage_init_connection(void) + +/* Sets up the buffers and primes the rx endpoint for the first command. + * The buffers are allocated only once Rockbox has handed the storage over: + * the host configures the device before every thread has acknowledged the + * connect, while for example the recording buffer still holds all free + * memory. Until then the host's first command waits at the endpoint. */ +static void start_receiving_commands(void) { - logf("ums: set config"); /* prime rx endpoint. We only need room for commands */ state = WAITING_FOR_COMMAND; @@ -456,6 +474,9 @@ static int usb_storage_init_connection(void) #else unsigned char * buffer; + if (usb_handle > 0) + return; /* already receiving */ + // Add 31 to handle worst-case misalignment usb_handle = core_alloc_ex(ALLOCATE_BUFFER_SIZE + MAX_CBW_SIZE + 31, &buflib_ops_locked); @@ -475,6 +496,25 @@ static int usb_storage_init_connection(void) #endif #endif usb_drv_recv_nonblocking(EP_OUT, cbw_buffer, MAX_CBW_SIZE); +} + +static bool receiving_commands(void) +{ +#ifdef USB_STATIC_ALLOC + return true; +#else + return usb_handle > 0; +#endif +} + +static int usb_storage_init_connection(void) +{ + logf("ums: set config"); + state = WAITING_FOR_COMMAND; +#ifndef USB_STATIC_ALLOC + if(usb_exclusive_storage()) +#endif + start_receiving_commands(); int i; for(i=0;ibRequest) { case USB_BULK_GET_MAX_LUN: { - *tb.max_lun = storage_num_drives() - 1; + /* comes before the handover, without the transfer buffer */ + reqdata[0] = storage_num_drives() - 1; #if defined(HAVE_MULTIDRIVE) - if(skip_first) (*tb.max_lun) --; + if(skip_first) reqdata[0]--; #endif logf("ums: getmaxlun"); - usb_core_control_response(USB_CONTROL_ACK, tb.max_lun, 1); + usb_core_control_response(USB_CONTROL_ACK, reqdata, 1); handled = true; break; } case USB_BULK_RESET_REQUEST: logf("ums: bulk reset"); + if(state == WAITING_FOR_STORAGE) { + /* Discard the retained CBW on host reset and accept a new + * one; do not replay the cancelled command when ready. */ + usb_drv_recv_nonblocking(EP_OUT, cbw_buffer, MAX_CBW_SIZE); + } state = WAITING_FOR_COMMAND; /* UMS BOT 3.1 says The device shall preserve the value of its bulk data toggle bits and endpoint STALL conditions despite @@ -767,7 +821,7 @@ static void send_and_read_next(void) } /****************************************************************************/ -static void handle_scsi(struct command_block_wrapper* cbw) +static void handle_scsi_ready(struct command_block_wrapper* cbw) { struct storage_info info; unsigned int length = cbw->data_transfer_length; @@ -814,6 +868,7 @@ static void handle_scsi(struct command_block_wrapper* cbw) uint32_t bsize = block_size*block_size_mult; sector_t bcount = block_count/block_size_mult; + cur_cmd.data_residue = letoh32(cbw->data_transfer_length); cur_cmd.tag = cbw->tag; cur_cmd.lun = lun; cur_cmd.cur_cmd = cbw->command_block[0]; @@ -821,13 +876,6 @@ static void handle_scsi(struct command_block_wrapper* cbw) switch (cbw->command_block[0]) { case SCSI_TEST_UNIT_READY: logf("scsi test_unit_ready %d",lun); - if(!usb_exclusive_storage()) { - send_csw(UMS_STATUS_FAIL); - cur_sense_data.sense_key=SENSE_NOT_READY; - cur_sense_data.asc=ASC_MEDIUM_NOT_PRESENT; - cur_sense_data.ascq=0; - break; - } if(lun_present) { send_csw(UMS_STATUS_GOOD); } @@ -905,7 +953,8 @@ static void handle_scsi(struct command_block_wrapper* cbw) tb.ms_data_10->mode_data_length = htobe16(sizeof(struct mode_sense_data_10)-2); tb.ms_data_10->medium_type = 0; - tb.ms_data_10->device_specific = 0; + tb.ms_data_10->device_specific = + LUN_READONLY(lun) ? 0x80 : 0; tb.ms_data_10->reserved = 0; tb.ms_data_10->longlba = 1; tb.ms_data_10->block_descriptor_length = @@ -969,7 +1018,8 @@ static void handle_scsi(struct command_block_wrapper* cbw) tb.ms_data_6->mode_data_length = sizeof(struct mode_sense_data_6)-1; tb.ms_data_6->medium_type = 0; - tb.ms_data_6->device_specific = 0; + tb.ms_data_6->device_specific = + LUN_READONLY(lun) ? 0x80 : 0; tb.ms_data_6->block_descriptor_length = sizeof(struct mode_sense_bdesc_shortlba); tb.ms_data_6->block_descriptor.density_code = 0; @@ -1012,6 +1062,13 @@ static void handle_scsi(struct command_block_wrapper* cbw) logf("scsi load/eject"); if((cbw->command_block[4] & 0x01) == 0) /* Don't start */ { +#ifdef HAVE_STORAGE_FLUSH + /* The host stops or ejects the unit when it is done with + * it - "safely remove" - and may cut power next. Commit + * what the storage driver still holds in RAM, such as a + * flash translation layer's part-written pages. */ + storage_flush(); +#endif if((cbw->command_block[4] & 0x02) != 0) /* eject */ { logf("scsi eject"); @@ -1249,6 +1306,13 @@ static void handle_scsi(struct command_block_wrapper* cbw) cur_sense_data.ascq=0; break; } + if(LUN_READONLY(lun)) { + send_csw(UMS_STATUS_FAIL); + cur_sense_data.sense_key=SENSE_DATA_PROTECT; + cur_sense_data.asc=ASC_WRITE_PROTECTED; + cur_sense_data.ascq=0; + break; + } cur_cmd.data[0] = tb.transfer_buffer; cur_cmd.data[1] = &tb.transfer_buffer[WRITE_BUFFER_SIZE]; cur_cmd.data_select=0; @@ -1284,6 +1348,13 @@ static void handle_scsi(struct command_block_wrapper* cbw) cur_sense_data.ascq=0; break; } + if(LUN_READONLY(lun)) { + send_csw(UMS_STATUS_FAIL); + cur_sense_data.sense_key=SENSE_DATA_PROTECT; + cur_sense_data.asc=ASC_WRITE_PROTECTED; + cur_sense_data.ascq=0; + break; + } cur_cmd.data[0] = tb.transfer_buffer; cur_cmd.data[1] = &tb.transfer_buffer[WRITE_BUFFER_SIZE]; cur_cmd.data_select=0; @@ -1333,7 +1404,9 @@ static void handle_scsi(struct command_block_wrapper* cbw) && (cbw->command_block[8]==0 || cbw->command_block[8]==0x0c) && cbw->command_block[9]==0) - receive_time(); + receive_time(); + else + reject_command(cbw); break; #endif /* CONFIG_RTC */ @@ -1381,14 +1454,68 @@ static void handle_scsi(struct command_block_wrapper* cbw) #endif /* STORAGE_ATA */ default: logf("scsi unknown cmd %x",cbw->command_block[0x0]); - send_csw(UMS_STATUS_FAIL); - cur_sense_data.sense_key=SENSE_ILLEGAL_REQUEST; - cur_sense_data.asc=ASC_INVALID_COMMAND; - cur_sense_data.ascq=0; + reject_command(cbw); break; } } +/* SET_CONFIGURATION now precedes the storage handover. Keep the first + * CBW pending until all Rockbox clients have acknowledged/unmounted. This + * avoids falsely reporting an absent disk to a fast host and, importantly, + * prevents READ/WRITE commands from racing Rockbox filesystem users. The + * OUT endpoint is not rearmed until this CBW has received its CSW. */ +static void handle_scsi(struct command_block_wrapper* cbw) +{ + if(!usb_exclusive_storage()) { + state = WAITING_FOR_STORAGE; + return; + } + handle_scsi_ready(cbw); +} + +static void usb_storage_notify_event(intptr_t data) +{ + (void)data; + if(!receiving_commands()) { + if(usb_exclusive_storage()) + start_receiving_commands(); + return; + } + if(state == WAITING_FOR_STORAGE && usb_exclusive_storage()) + handle_scsi((struct command_block_wrapper*)cbw_buffer); +} + +/* Count data only, never the CBW or CSW. A short result must report the + * untransferred bytes instead of claiming that the whole request succeeded. */ +static void account_data_transfer(int dir, int status, int length) +{ + bool data_in = state == SENDING_RESULT || state == SENDING_FAILED_RESULT || + state == SENDING_BLOCKS; + bool data_out = state == RECEIVING_BLOCKS; +#if CONFIG_RTC + data_out |= state == RECEIVING_TIME; +#endif + if(status == 0 && length > 0 && + ((data_in && dir == USB_DIR_IN) || (data_out && dir == USB_DIR_OUT))) + cur_cmd.data_residue -= MIN(cur_cmd.data_residue, (unsigned int)length); +} + +/* BOT cases Hi/Dn and Ho/Dn still have a data phase. Sending the CSW as + * IN data makes the host wait for a second status that will never arrive. */ +static void reject_command(const struct command_block_wrapper *cbw) +{ + cur_sense_data.sense_key = SENSE_ILLEGAL_REQUEST; + cur_sense_data.asc = ASC_INVALID_COMMAND; + cur_sense_data.ascq = 0; + if(letoh32(cbw->data_transfer_length)) { + bool in = (cbw->flags & USB_DIR_IN) != 0; + /* Halt the data pipe first. The host clears it before reading CSW + * (BOT 6.7.2 case 4 / 6.7.3 case 9). Queue CSW behind that halt. */ + usb_drv_stall(in ? EP_IN : EP_OUT, true, in); + } + send_csw(UMS_STATUS_FAIL); +} + static void send_block_data(void *data,int size) { usb_drv_send_nonblocking(EP_IN, data,size); @@ -1425,7 +1552,7 @@ static void send_csw(int status) { tb.csw->signature = htole32(CSW_SIGNATURE); tb.csw->tag = cur_cmd.tag; - tb.csw->data_residue = 0; + tb.csw->data_residue = htole32(cur_cmd.data_residue); tb.csw->status = status; usb_drv_send_nonblocking(EP_IN, tb.csw, @@ -1488,6 +1615,7 @@ struct usb_class_driver usb_cdrv_storage = { .disconnect = usb_storage_disconnect, .transfer_complete = usb_storage_transfer_complete, .control_request = usb_storage_control_request, + .notify_event = usb_storage_notify_event, #ifdef HAVE_HOTSWAP .notify_hotswap = usb_storage_notify_hotswap, #endif diff --git a/lib/rbcodec/codecs/SOURCES b/lib/rbcodec/codecs/SOURCES index 44b79087fe..cc097546d4 100644 --- a/lib/rbcodec/codecs/SOURCES +++ b/lib/rbcodec/codecs/SOURCES @@ -47,7 +47,8 @@ kss.c #endif aac_bsf.c -#ifdef HAVE_RECORDING +/* warble is decoder-only */ +#if defined(HAVE_RECORDING) && !defined(WARBLE) /* encoders */ @@ -56,4 +57,4 @@ mp3_enc.c wav_enc.c wavpack_enc.c -#endif /* HAVE_RECORDING */ +#endif /* HAVE_RECORDING && !WARBLE */ diff --git a/lib/rbcodec/codecs/a52.c b/lib/rbcodec/codecs/a52.c index 7470148efe..b6e6c17b81 100644 --- a/lib/rbcodec/codecs/a52.c +++ b/lib/rbcodec/codecs/a52.c @@ -158,14 +158,15 @@ enum codec_status codec_run(void) samplesdone = 0; - if (ci->id3->offset) { - sample_loc = (ci->id3->offset / ci->id3->bytesperframe) * - A52_SAMPLESPERFRAME; + if (ci->id3->offset > ci->id3->first_frame_offset) { + sample_loc = ((ci->id3->offset - ci->id3->first_frame_offset) / + ci->id3->bytesperframe) * A52_SAMPLESPERFRAME; param = ci->id3->offset; } else if (ci->id3->elapsed) { sample_loc = ci->id3->elapsed/1000 * ci->id3->frequency; - param = sample_loc/A52_SAMPLESPERFRAME*ci->id3->bytesperframe; + param = sample_loc/A52_SAMPLESPERFRAME*ci->id3->bytesperframe + + ci->id3->first_frame_offset; } else { sample_loc = 0; @@ -190,7 +191,8 @@ enum codec_status codec_run(void) sample_loc = param/1000 * ci->id3->frequency; if (ci->seek_buffer((sample_loc/A52_SAMPLESPERFRAME)* - ci->id3->bytesperframe)) { + ci->id3->bytesperframe + + ci->id3->first_frame_offset)) { samplesdone = sample_loc; ci->set_elapsed(samplesdone/(ci->id3->frequency/1000)); } diff --git a/lib/rbcodec/codecs/atrac3_oma.c b/lib/rbcodec/codecs/atrac3_oma.c index ede11840d1..e9d9dda3c6 100644 --- a/lib/rbcodec/codecs/atrac3_oma.c +++ b/lib/rbcodec/codecs/atrac3_oma.c @@ -63,7 +63,7 @@ enum codec_status codec_run(void) ci->memset(&q,0,sizeof(ATRAC3Context)); ci->configure(DSP_SET_FREQUENCY, ci->id3->frequency); - ci->configure(DSP_SET_SAMPLE_DEPTH, 17); /* Remark: atrac3 uses s15.0 by default, s15.2 was hacked. */ + ci->configure(DSP_SET_SAMPLE_DEPTH, ATRAC3_OUTPUT_DEPTH); const uint8_t channels = 2; ci->configure(DSP_SET_STEREO_MODE, channels == 1 ? STEREO_MONO : STEREO_NONINTERLEAVED); diff --git a/lib/rbcodec/codecs/atrac3_rm.c b/lib/rbcodec/codecs/atrac3_rm.c index 0ce961fd96..1d42beb11d 100644 --- a/lib/rbcodec/codecs/atrac3_rm.c +++ b/lib/rbcodec/codecs/atrac3_rm.c @@ -92,7 +92,7 @@ enum codec_status codec_run(void) init_rm(&rmctx); ci->configure(DSP_SET_FREQUENCY, ci->id3->frequency); - ci->configure(DSP_SET_SAMPLE_DEPTH, 17); /* Remark: atrac3 uses s15.0 by default, s15.2 was hacked. */ + ci->configure(DSP_SET_SAMPLE_DEPTH, ATRAC3_OUTPUT_DEPTH); ci->configure(DSP_SET_STEREO_MODE, rmctx.nb_channels == 1 ? STEREO_MONO : STEREO_NONINTERLEAVED); @@ -107,6 +107,10 @@ enum codec_status codec_run(void) scrambling_unit_size = h * (fs + packet_header_size); spn = h * fs / sps; + /* The decoder takes its frame size from the id3, which the RM + * metadata parser does not fill in. */ + ci->id3->bytesperframe = sps; + res = atrac3_decode_init(&q, ci->id3, rmctx.nb_channels, rmctx.extradata_size); if(res < 0) { DEBUGF("failed to initialize RM atrac decoder\n"); diff --git a/lib/rbcodec/codecs/codec_crt0.c b/lib/rbcodec/codecs/codec_crt0.c index d3bde77339..831af0924b 100644 --- a/lib/rbcodec/codecs/codec_crt0.c +++ b/lib/rbcodec/codecs/codec_crt0.c @@ -30,7 +30,7 @@ extern unsigned char plugin_end_addr[]; * avoid warning with certain compilers */ int _start(void) {return 0;} -#ifdef CTRU +#ifdef N3DS /* dummy undefined symbols */ void __aeabi_unwind_cpp_pr0(void) {} struct _reent * _EXFUN(__getreent, (void)) { return NULL; } diff --git a/lib/rbcodec/codecs/codecs.make b/lib/rbcodec/codecs/codecs.make index 53233f7f02..f0c227a136 100644 --- a/lib/rbcodec/codecs/codecs.make +++ b/lib/rbcodec/codecs/codecs.make @@ -17,8 +17,9 @@ CODECS := $(subst $(RBCODECLIB_DIR),$(RBCODEC_BLD),$(CODECS)) include $(RBCODECLIB_DIR)/codecs/lib/libcodec.make OTHER_INC += -I$(RBCODECLIB_DIR)/codecs/lib -# extra libraries -CODEC_LIBS := $(CODECLIB) $(FIXEDPOINTLIB) +# extra libraries. libarm_support has the division routines, which are much +# faster than what libgcc may supply; it is empty when not building for ARM. +CODEC_LIBS := $(CODECLIB) $(FIXEDPOINTLIB) $(ARMSUPPORTLIB) # compile flags for codecs CODECFLAGS := $(CFLAGS) $(RBCODEC_CFLAGS) -fstrict-aliasing \ diff --git a/lib/rbcodec/codecs/demac/libdemac/entropy.c b/lib/rbcodec/codecs/demac/libdemac/entropy.c index 44b460fe9f..dc4448e34c 100644 --- a/lib/rbcodec/codecs/demac/libdemac/entropy.c +++ b/lib/rbcodec/codecs/demac/libdemac/entropy.c @@ -121,21 +121,37 @@ each function (and the RNGC macro)). for aligned reads. */ -static unsigned char* bytebuffer IBSS_ATTR_DEMAC; -static int bytebufferoffset IBSS_ATTR_DEMAC; - -static inline void skip_byte(void) +/* The decoder's state. It lives in statics between calls, but while a + block is decoded it is copied to locals, so that the compiler can keep + it in registers: with every field in memory, loads and stores were about + half of the decode time on ARM7TDMI. That only works if everything + that takes a pointer to it is inlined, which the compiler does not + always choose to do by itself. */ +#define DEMAC_INLINE static inline __attribute__((always_inline)) +struct rangecoder_t { - bytebufferoffset--; - bytebuffer += bytebufferoffset & 4; - bytebufferoffset &= 3; + uint32_t low; /* low end of interval */ + uint32_t range; /* length of interval */ + uint32_t help; /* bytes_to_follow resp. intermediate value */ + unsigned int buffer; /* buffer for input/output */ + unsigned char* bytebuffer; + int bytebufferoffset; +}; + +static struct rangecoder_t rc IBSS_ATTR_DEMAC; + +DEMAC_INLINE void skip_byte(struct rangecoder_t* rc) +{ + rc->bytebufferoffset--; + rc->bytebuffer += rc->bytebufferoffset & 4; + rc->bytebufferoffset &= 3; } -static inline int read_byte(void) +DEMAC_INLINE int read_byte(struct rangecoder_t* rc) { - int ch = bytebuffer[bytebufferoffset]; + int ch = rc->bytebuffer[rc->bytebufferoffset]; - skip_byte(); + skip_byte(rc); return ch; } @@ -150,119 +166,129 @@ static inline int read_byte(void) #define EXTRA_BITS ((CODE_BITS-2) % 8 + 1) #define BOTTOM_VALUE (TOP_VALUE >> 8) -struct rangecoder_t -{ - uint32_t low; /* low end of interval */ - uint32_t range; /* length of interval */ - uint32_t help; /* bytes_to_follow resp. intermediate value */ - unsigned int buffer; /* buffer for input/output */ -}; - -static struct rangecoder_t rc IBSS_ATTR_DEMAC; - /* Start the decoder */ -static inline void range_start_decoding(void) +DEMAC_INLINE void range_start_decoding(struct rangecoder_t* rc) { - rc.buffer = read_byte(); - rc.low = rc.buffer >> (8 - EXTRA_BITS); - rc.range = (uint32_t) 1 << EXTRA_BITS; + rc->buffer = read_byte(rc); + rc->low = rc->buffer >> (8 - EXTRA_BITS); + rc->range = (uint32_t) 1 << EXTRA_BITS; } -static inline void range_dec_normalize(void) +DEMAC_INLINE void range_dec_normalize(struct rangecoder_t* rc) { - while (rc.range <= BOTTOM_VALUE) - { - rc.buffer = (rc.buffer << 8) | read_byte(); - rc.low = (rc.low << 8) | ((rc.buffer >> 1) & 0xff); - rc.range <<= 8; + while (rc->range <= BOTTOM_VALUE) + { + rc->buffer = (rc->buffer << 8) | read_byte(rc); + rc->low = (rc->low << 8) | ((rc->buffer >> 1) & 0xff); + rc->range <<= 8; } } +/* range / tot_f. tot_f is the Rice pivot, which is small (below 1024 + for nearly every sample of ordinary 16-bit audio), so where a 32x32->64 + multiply is cheap its reciprocal comes from a table that is filled in as + divisors turn up, and the division is a multiply and a correction. The + other division, by help, has no such pattern. */ +#if defined(CPU_ARM) && ARM_ARCH >= 5 && !defined(ARM_HAVE_HW_DIV) +#define RECIP_ENTRIES 4096 +/* recip[n] is (2^32 - 1) / n, which is never 0: 0 is "not worked out" */ +static uint32_t recip[RECIP_ENTRIES]; + +DEMAC_INLINE uint32_t range_div(uint32_t range, uint32_t tot_f) +{ + uint32_t r, q; + + if (UNLIKELY(tot_f >= RECIP_ENTRIES)) + return UDIV32(range, tot_f); + + r = recip[tot_f]; + if (UNLIKELY(r == 0)) + r = recip[tot_f] = UDIV32(0xffffffff, tot_f); + + /* this is the quotient or one less */ + q = ((uint64_t)range * r) >> 32; + if (range - q * tot_f >= tot_f) + q++; + return q; +} +#else +#define range_div(range, tot_f) UDIV32(range, tot_f) +#endif + /* Calculate culmulative frequency for next symbol. Does NO update!*/ /* tot_f is the total frequency */ /* or: totf is (code_value)1<help = range_div(rc->range, tot_f); + return UDIV32(rc->low, rc->help); } -static inline int range_decode_culshift(int shift) +DEMAC_INLINE int range_decode_culshift(struct rangecoder_t* rc, int shift) { - range_dec_normalize(); - rc.help = rc.range >> shift; - return UDIV32(rc.low, rc.help); + range_dec_normalize(rc); + rc->help = rc->range >> shift; + return UDIV32(rc->low, rc->help); } /* Update decoding state */ /* sy_f is the interval length (frequency of the symbol) */ /* lt_f is the lower end (frequency sum of < symbols) */ -static inline void range_decode_update(int sy_f, int lt_f) +DEMAC_INLINE void range_decode_update(struct rangecoder_t* rc, + int sy_f, int lt_f) { - rc.low -= rc.help * lt_f; - rc.range = rc.help * sy_f; + rc->low -= rc->help * lt_f; + rc->range = rc->help * sy_f; } /* Decode a byte/short without modelling */ -static inline unsigned char decode_byte(void) -{ int tmp = range_decode_culshift(8); - range_decode_update( 1,tmp); - return tmp; -} - -static inline unsigned short range_decode_short(void) -{ int tmp = range_decode_culshift(16); - range_decode_update( 1,tmp); +DEMAC_INLINE unsigned short range_decode_short(struct rangecoder_t* rc) +{ int tmp = range_decode_culshift(rc, 16); + range_decode_update(rc, 1, tmp); return tmp; } /* Decode n bits (n <= 16) without modelling - based on range_decode_short */ -static inline int range_decode_bits(int n) -{ int tmp = range_decode_culshift(n); - range_decode_update( 1,tmp); +DEMAC_INLINE int range_decode_bits(struct rangecoder_t* rc, int n) +{ int tmp = range_decode_culshift(rc, n); + range_decode_update(rc, 1, tmp); return tmp; } /* Finish decoding */ -static inline void range_done_decoding(void) -{ range_dec_normalize(); /* normalize to use up all bytes */ +DEMAC_INLINE void range_done_decoding(struct rangecoder_t* rc) +{ range_dec_normalize(rc); /* normalize to use up all bytes */ } /* - range_get_symbol_* functions based on main decoding loop in simple_d.c from + range_get_symbol() is based on the main decoding loop in simple_d.c from http://www.compressconsult.com/rangecoder/rngcod13.zip (c) Michael Schindler + + That divides low by help for the cumulative frequency cf and then looks + for the last symbol whose count is not above it. counts[n] <= cf is the + same as counts[n] * help <= low, so the division is not needed: the search + multiplies instead. The first few symbols are by far the likeliest, so + it is short. No count is above 65536 and help is below it here, so the + product cannot overflow. */ - -static inline int range_get_symbol_3980(void) +DEMAC_INLINE int range_get_symbol(struct rangecoder_t* rc, + const int* counts, const int* counts_diff) { - int symbol, cf; + int symbol; - cf = range_decode_culshift(16); + range_dec_normalize(rc); + rc->help = rc->range >> 16; - /* figure out the symbol inefficiently; a binary search would be much better */ - for (symbol = 0; counts_3980[symbol+1] <= cf; symbol++); + for (symbol = 0; (uint32_t)counts[symbol+1] * rc->help <= rc->low; + symbol++); - range_decode_update(counts_diff_3980[symbol],counts_3980[symbol]); - - return symbol; -} - -static inline int range_get_symbol_3970(void) -{ - int symbol, cf; - - cf = range_decode_culshift(16); - - /* figure out the symbol inefficiently; a binary search would be much better */ - for (symbol = 0; counts_3970[symbol+1] <= cf; symbol++); - - range_decode_update(counts_diff_3970[symbol],counts_3970[symbol]); + range_decode_update(rc, counts_diff[symbol], counts[symbol]); return symbol; } @@ -278,7 +304,7 @@ struct rice_t static struct rice_t riceX IBSS_ATTR_DEMAC; static struct rice_t riceY IBSS_ATTR_DEMAC; -static inline void update_rice(struct rice_t* rice, int x) +DEMAC_INLINE void update_rice(struct rice_t* rice, int x) { rice->ksum += ((x + 1) / 2) - ((rice->ksum + 16) >> 5); @@ -294,7 +320,8 @@ static inline void update_rice(struct rice_t* rice, int x) } } -static inline int entropy_decode3980(struct rice_t* rice) +DEMAC_INLINE int entropy_decode3980(struct rangecoder_t* rc, + struct rice_t* rice) { int base, x, pivot, overflow; @@ -302,11 +329,11 @@ static inline int entropy_decode3980(struct rice_t* rice) if (UNLIKELY(pivot == 0)) pivot=1; - overflow = range_get_symbol_3980(); + overflow = range_get_symbol(rc, counts_3980, counts_diff_3980); if (UNLIKELY(overflow == (MODEL_ELEMENTS-1))) { - overflow = range_decode_short() << 16; - overflow |= range_decode_short(); + overflow = range_decode_short(rc) << 16; + overflow |= range_decode_short(rc); } if (pivot >= 0x10000) { @@ -322,17 +349,17 @@ static inline int entropy_decode3980(struct rice_t* rice) */ lo_bits = (nbits - 16); - base_hi = range_decode_culfreq((pivot >> lo_bits) + 1); - range_decode_update(1, base_hi); + base_hi = range_decode_culfreq(rc, (pivot >> lo_bits) + 1); + range_decode_update(rc, 1, base_hi); - base_lo = range_decode_culshift(lo_bits); - range_decode_update(1, base_lo); + base_lo = range_decode_culshift(rc, lo_bits); + range_decode_update(rc, 1, base_lo); base = (base_hi << lo_bits) + base_lo; } else { /* Codepath for 16-bit streams */ - base = range_decode_culfreq(pivot); - range_decode_update(1, base); + base = range_decode_culfreq(rc, pivot); + range_decode_update(rc, 1, base); } x = base + (overflow * pivot); @@ -346,24 +373,25 @@ static inline int entropy_decode3980(struct rice_t* rice) } -static inline int entropy_decode3970(struct rice_t* rice) +DEMAC_INLINE int entropy_decode3970(struct rangecoder_t* rc, + struct rice_t* rice) { int x, tmpk; - int overflow = range_get_symbol_3970(); + int overflow = range_get_symbol(rc, counts_3970, counts_diff_3970); if (UNLIKELY(overflow == (MODEL_ELEMENTS - 1))) { - tmpk = range_decode_bits(5); + tmpk = range_decode_bits(rc, 5); overflow = 0; } else { tmpk = (rice->k < 1) ? 0 : rice->k - 1; } if (tmpk <= 16) { - x = range_decode_bits(tmpk); + x = range_decode_bits(rc, tmpk); } else { - x = range_decode_short(); - x |= (range_decode_bits(tmpk - 16) << 16); + x = range_decode_short(rc); + x |= (range_decode_bits(rc, tmpk - 16) << 16); } x += (overflow << tmpk); @@ -380,24 +408,24 @@ void init_entropy_decoder(struct ape_ctx_t* ape_ctx, unsigned char* inbuffer, int* firstbyte, int* bytesconsumed) { - bytebuffer = inbuffer; - bytebufferoffset = *firstbyte; + rc.bytebuffer = inbuffer; + rc.bytebufferoffset = *firstbyte; /* Read the CRC */ - ape_ctx->CRC = read_byte(); - ape_ctx->CRC = (ape_ctx->CRC << 8) | read_byte(); - ape_ctx->CRC = (ape_ctx->CRC << 8) | read_byte(); - ape_ctx->CRC = (ape_ctx->CRC << 8) | read_byte(); + ape_ctx->CRC = read_byte(&rc); + ape_ctx->CRC = (ape_ctx->CRC << 8) | read_byte(&rc); + ape_ctx->CRC = (ape_ctx->CRC << 8) | read_byte(&rc); + ape_ctx->CRC = (ape_ctx->CRC << 8) | read_byte(&rc); /* Read the frame flags if they exist */ ape_ctx->frameflags = 0; if ((ape_ctx->fileversion > 3820) && (ape_ctx->CRC & 0x80000000)) { ape_ctx->CRC &= ~0x80000000; - ape_ctx->frameflags = read_byte(); - ape_ctx->frameflags = (ape_ctx->frameflags << 8) | read_byte(); - ape_ctx->frameflags = (ape_ctx->frameflags << 8) | read_byte(); - ape_ctx->frameflags = (ape_ctx->frameflags << 8) | read_byte(); + ape_ctx->frameflags = read_byte(&rc); + ape_ctx->frameflags = (ape_ctx->frameflags << 8) | read_byte(&rc); + ape_ctx->frameflags = (ape_ctx->frameflags << 8) | read_byte(&rc); + ape_ctx->frameflags = (ape_ctx->frameflags << 8) | read_byte(&rc); } /* Keep a count of the blocks decoded in this frame */ ape_ctx->blocksdecoded = 0; @@ -409,13 +437,13 @@ void init_entropy_decoder(struct ape_ctx_t* ape_ctx, riceY.ksum = (1 << riceY.k) * 16; /* The first 8 bits of input are ignored. */ - skip_byte(); + skip_byte(&rc); - range_start_decoding(); + range_start_decoding(&rc); /* Return the new state of the buffer */ - *bytesconsumed = (intptr_t)bytebuffer - (intptr_t)inbuffer; - *firstbyte = bytebufferoffset; + *bytesconsumed = (intptr_t)rc.bytebuffer - (intptr_t)inbuffer; + *firstbyte = rc.bytebufferoffset; } void ICODE_ATTR_DEMAC entropy_decode(struct ape_ctx_t* ape_ctx, @@ -424,8 +452,12 @@ void ICODE_ATTR_DEMAC entropy_decode(struct ape_ctx_t* ape_ctx, int32_t* decoded0, int32_t* decoded1, int blockstodecode) { - bytebuffer = inbuffer; - bytebufferoffset = *firstbyte; + /* local copies, for the compiler to keep in registers */ + struct rangecoder_t r = rc; + struct rice_t rx = riceX, ry = riceY; + + r.bytebuffer = inbuffer; + r.bytebufferoffset = *firstbyte; ape_ctx->blocksdecoded += blockstodecode; @@ -439,25 +471,29 @@ void ICODE_ATTR_DEMAC entropy_decode(struct ape_ctx_t* ape_ctx, } else { if (ape_ctx->fileversion > 3970) { while (LIKELY(blockstodecode--)) { - *(decoded0++) = entropy_decode3980(&riceY); + *(decoded0++) = entropy_decode3980(&r, &ry); if (decoded1 != NULL) - *(decoded1++) = entropy_decode3980(&riceX); + *(decoded1++) = entropy_decode3980(&r, &rx); } } else { while (LIKELY(blockstodecode--)) { - *(decoded0++) = entropy_decode3970(&riceY); + *(decoded0++) = entropy_decode3970(&r, &ry); if (decoded1 != NULL) - *(decoded1++) = entropy_decode3970(&riceX); + *(decoded1++) = entropy_decode3970(&r, &rx); } } } if (ape_ctx->blocksdecoded == ape_ctx->currentframeblocks) { - range_done_decoding(); + range_done_decoding(&r); } + rc = r; + riceX = rx; + riceY = ry; + /* Return the new state of the buffer */ - *bytesconsumed = bytebuffer - inbuffer; - *firstbyte = bytebufferoffset; + *bytesconsumed = r.bytebuffer - inbuffer; + *firstbyte = r.bytebufferoffset; } diff --git a/lib/rbcodec/codecs/flac.c b/lib/rbcodec/codecs/flac.c index 0a9d535c3e..c2fd70c896 100644 --- a/lib/rbcodec/codecs/flac.c +++ b/lib/rbcodec/codecs/flac.c @@ -75,6 +75,22 @@ static struct FLACseekpoints seekpoints[MAX_SUPPORTED_SEEKTABLE_SIZE]; static int nseekpoints; static int8_t *bit_buffer; +/* Frame copy buffer for flac_request_frame(). Targets with 2MB of RAM or + less (e.g. the Clip v1) cannot spare it and rely on the overrun check in + codec_run() alone. */ +#if MEMORYSIZE > 2 +#define FLAC_FRAME_COPY +#if MAX_BLOCKSIZE > 4608 +#define FRAME_COPY_SIZE MAX_FRAMESIZE +#else +/* Blocks are capped at 4608 samples here (see bitstream.h): size for the + largest stereo frame, 24 bit with verbatim subframes, plus headers. */ +#define FRAME_COPY_SIZE (MAX_BLOCKSIZE * 2 * 3 + 64) +#endif +/* Largest frame the current stream can contain */ +static size_t frame_bound; +static uint8_t frame_copy[FRAME_COPY_SIZE]; +#endif static size_t buff_size; static bool flac_init(FLACContext* fc, int first_frame_offset) @@ -209,14 +225,49 @@ static bool flac_init(FLACContext* fc, int first_frame_offset) } if (found_streaminfo) { - fc->bitrate = ((int64_t) (fc->filesize-fc->metadatalength) * 8) - / fc->length; +#ifdef FLAC_FRAME_COPY + frame_bound = fc->max_framesize; + if (frame_bound == 0) { + /* stream did not encode max frame size, assume worst case of + verbatim subframes plus headers */ + frame_bound = ((size_t)fc->max_blocksize * fc->channels * fc->bps + + 7) / 8 + 64; + } + if (frame_bound > FRAME_COPY_SIZE) + frame_bound = FRAME_COPY_SIZE; +#endif + /* length is 0 when STREAMINFO has no total sample count */ + fc->bitrate = fc->length ? ((int64_t) (fc->filesize-fc->metadatalength) * 8) + / fc->length : 0; return true; } else { return false; } } +/* request_buffer() only guarantees 32KiB of contiguous data, but a FLAC frame + can be larger, and the frame decoder cannot refill its buffer mid-frame. + When a request returns less than frame_bound before the end of the file, + the frame is copied into frame_copy and decoded from there. Frames + normally fit, so the usual cost is one comparison per frame. */ +#ifdef FLAC_FRAME_COPY +static void *flac_request_frame(size_t *len, size_t reqsize) +{ + void *buf = ci->request_buffer(len, reqsize); + + if (*len >= frame_bound || *len >= (size_t)(ci->filesize - ci->curpos)) + return buf; + + off_t pos = ci->curpos; + size_t got = ci->read_filebuf(frame_copy, frame_bound); + ci->seek_buffer(pos); + *len = got; + return frame_copy; +} +#else +#define flac_request_frame(len, reqsize) ci->request_buffer(len, reqsize) +#endif + /* Synchronize to next frame in stream - adapted from libFLAC 1.1.3b2 */ static bool frame_sync(FLACContext* fc) { unsigned int x = 0; @@ -255,7 +306,7 @@ static bool frame_sync(FLACContext* fc) { /* Advance and init bit buffer to the new frame. */ ci->advance_buffer((get_bits_count(&fc->gb)-16)>>3); /* consumed bytes */ - bit_buffer = ci->request_buffer(&buff_size, MAX_FRAMESIZE+16); + bit_buffer = flac_request_frame(&buff_size, MAX_FRAMESIZE+16); init_get_bits(&fc->gb, bit_buffer, buff_size*8); /* Decode the frame to verify the frame crc and @@ -490,9 +541,7 @@ enum codec_status codec_run(void) ci->configure(DSP_SET_STEREO_MODE, fc.channels == 1 ? STEREO_MONO : STEREO_NONINTERLEAVED); codec_set_replaygain(ci->id3); - - if (samplesdone || !elapsedtime) { - flac_seek_offset(&fc, samplesdone); + if (samplesdone && flac_seek_offset(&fc, samplesdone)) { samplesdone=fc.samplenumber+fc.blocksize; elapsedtime=((uint64_t)samplesdone*1000)/(ci->id3->frequency); } @@ -504,7 +553,7 @@ enum codec_status codec_run(void) ci->set_elapsed(elapsedtime); /* The main decoding loop */ - buf = ci->request_buffer(&bytesleft, MAX_FRAMESIZE); + buf = flac_request_frame(&bytesleft, MAX_FRAMESIZE); while (bytesleft) { long action = ci->get_command(¶m); @@ -516,7 +565,7 @@ enum codec_status codec_run(void) if (flac_seek(&fc,(uint32_t)(((uint64_t)param *ci->id3->frequency)/1000))) { /* Refill the input buffer */ - buf = ci->request_buffer(&bytesleft, MAX_FRAMESIZE); + buf = flac_request_frame(&bytesleft, MAX_FRAMESIZE); } ci->set_elapsed(param); @@ -529,6 +578,10 @@ enum codec_status codec_run(void) return CODEC_ERROR; } consumed=fc.gb.index/8; + if (res == 0 && consumed > (int)bytesleft) { + LOGF("FLAC: Frame overran its %d bytes of data\n", (int)bytesleft); + return CODEC_ERROR; + } #if defined(LOGF_ENABLE) frame++; #endif @@ -546,7 +599,7 @@ enum codec_status codec_run(void) ci->advance_buffer(consumed); - buf = ci->request_buffer(&bytesleft, MAX_FRAMESIZE); + buf = flac_request_frame(&bytesleft, MAX_FRAMESIZE); } LOGF("FLAC: Decoded %lu samples\n",(unsigned long)samplesdone); diff --git a/lib/rbcodec/codecs/libatrac/atrac3.c b/lib/rbcodec/codecs/libatrac/atrac3.c index f0098258b3..f814b2b626 100644 --- a/lib/rbcodec/codecs/libatrac/atrac3.c +++ b/lib/rbcodec/codecs/libatrac/atrac3.c @@ -86,6 +86,13 @@ static int vlcs_initialized = 0; int32_t *inlo, int32_t *inhi, unsigned int nIn); + /* The same, with the results scaled up by ATRAC3_OUT_SHIFT bits + * (ARMv5 and later) */ + extern void + atrac3_iqmf_matrixing_out(int32_t *p3, + int32_t *inlo, + int32_t *inhi, + unsigned int nIn); #else static inline void atrac3_iqmf_matrixing(int32_t *p3, @@ -101,6 +108,22 @@ static int vlcs_initialized = 0; p3[2*i+3] = inlo[i+1] - inhi[i+1]; } } + + /* The same, with the results scaled up by ATRAC3_OUT_SHIFT bits */ + static inline void + atrac3_iqmf_matrixing_out(int32_t *p3, + int32_t *inlo, + int32_t *inhi, + unsigned int nIn) + { + uint32_t i; + for(i=0; ifirst); - } else { - /* Even band - Do not reverse coefficients */ - do { - pOut[first++] = fixmul16(*pIn++, SF); - pOut[first++] = fixmul16(*pIn++, SF); - pOut[first++] = fixmul16(*pIn++, SF); - pOut[first++] = fixmul16(*pIn++, SF); - pOut[first++] = fixmul16(*pIn++, SF); - pOut[first++] = fixmul16(*pIn++, SF); - pOut[first++] = fixmul16(*pIn++, SF); - pOut[first++] = fixmul16(*pIn++, SF); - } while (firstspectrum[band*256]), pSnd->IMDCT_buf); + IMLT(&(pSnd->spectrum[band*256]), pSnd->IMDCT_buf, band & 1); } else { memset(pSnd->IMDCT_buf, 0, 512 * sizeof(int32_t)); } @@ -1104,9 +1153,9 @@ static int decodeFrame(ATRAC3Context *q, const uint8_t* databuf, int off) p2= p1+256; p3= p2+256; p4= p3+256; - iqmf (p1, p2, 256, p1, q->pUnits[i].delayBuf1, q->tempBuf); - iqmf (p4, p3, 256, p3, q->pUnits[i].delayBuf2, q->tempBuf); - iqmf (p1, p3, 512, p1, q->pUnits[i].delayBuf3, q->tempBuf); + iqmf (p1, p2, 256, p1, q->pUnits[i].delayBuf1, q->tempBuf, false); + iqmf (p4, p3, 256, p3, q->pUnits[i].delayBuf2, q->tempBuf, false); + iqmf (p1, p3, 512, p1, q->pUnits[i].delayBuf3, q->tempBuf, true); p1 +=1024; } diff --git a/lib/rbcodec/codecs/libatrac/atrac3.h b/lib/rbcodec/codecs/libatrac/atrac3.h index 68db7af9d1..661e4810aa 100644 --- a/lib/rbcodec/codecs/libatrac/atrac3.h +++ b/lib/rbcodec/codecs/libatrac/atrac3.h @@ -35,6 +35,13 @@ #define ICONST_ATTR_LARGE_IRAM #endif +/* The decoder works on samples with two fractional bits, s15.2. The last + * stage of the synthesis filter is scaled up by this many more bits, so the + * output has 28 bits below the sign like that of most codecs and the + * precision of that stage's multiplications is not thrown away. */ +#define ATRAC3_OUT_SHIFT 11 +#define ATRAC3_OUTPUT_DEPTH (17 + ATRAC3_OUT_SHIFT) + /* These structures are needed to store the parsed gain control data. */ typedef struct { int num_gain_data; diff --git a/lib/rbcodec/codecs/libatrac/atrac3_arm.S b/lib/rbcodec/codecs/libatrac/atrac3_arm.S index 3a81039d2b..70181e1352 100644 --- a/lib/rbcodec/codecs/libatrac/atrac3_arm.S +++ b/lib/rbcodec/codecs/libatrac/atrac3_arm.S @@ -71,6 +71,58 @@ atrac3_iqmf_matrixing: .atrac3_iqmf_matrixing_end: .size atrac3_iqmf_matrixing,.atrac3_iqmf_matrixing_end-atrac3_iqmf_matrixing + +/* ATRAC3_OUT_SHIFT of atrac3.h */ +#define OUT_SHIFT 11 + +/**************************************************************************** + * void atrac3_iqmf_matrixing_out(int32_t *dest, + * int32_t *inlo, + * int32_t *inhi, + * unsigned int count); + * + * The matrixing step of the last iqmf stage: as atrac3_iqmf_matrixing, with + * the results scaled up by OUT_SHIFT bits, which then is the scale of the + * decoder's output. Not used on ARMv4, where the multiplications of the + * dewindowing would take longer for it: see atrac3_iqmf_dewindowing_out. + ****************************************************************************/ +#if ARM_ARCH >= 5 + .align 2 + .global atrac3_iqmf_matrixing_out + .type atrac3_iqmf_matrixing_out, %function + +atrac3_iqmf_matrixing_out: + /* r0 = dest */ + /* r1 = inlo */ + /* r2 = inhi */ + /* r3 = counter */ + stmfd sp!, {r4-r9, lr} /* save non-scratch registers */ + +.iqmf_matrixing_out_loop: + ldmia r1!, { r4, r6, r8, r12} /* load inlo[0...3] */ + ldmia r2!, { r5, r7, r9, lr } /* load inhi[0...3] */ + add r4, r4, r5 /* r4 = inlo[0] + inhi[0] */ + mov r4, r4, asl #OUT_SHIFT + sub r5, r4, r5, asl #OUT_SHIFT+1 /* r5 = inlo[0] - inhi[0] */ + add r6, r6, r7 /* r6 = inlo[1] + inhi[1] */ + mov r6, r6, asl #OUT_SHIFT + sub r7, r6, r7, asl #OUT_SHIFT+1 /* r7 = inlo[1] - inhi[1] */ + add r8, r8, r9 /* r8 = inlo[2] + inhi[2] */ + mov r8, r8, asl #OUT_SHIFT + sub r9, r8, r9, asl #OUT_SHIFT+1 /* r9 = inlo[2] - inhi[2] */ + add r12, r12, lr /* r12 = inlo[3] + inhi[3] */ + mov r12, r12, asl #OUT_SHIFT + sub lr , r12, lr, asl #OUT_SHIFT+1 /* lr = inlo[3] - inhi[3] */ + stmia r0!, {r4-r9, r12, lr} /* store results to dest */ + subs r3, r3, #4 /* counter -= 4 */ + bgt .iqmf_matrixing_out_loop + + ldmpc regs=r4-r9 /* restore registers */ + +.atrac3_iqmf_matrixing_out_end: + .size atrac3_iqmf_matrixing_out,.atrac3_iqmf_matrixing_out_end-atrac3_iqmf_matrixing_out +#endif /* ARM_ARCH >= 5 */ + /**************************************************************************** * atrac3_iqmf_dewindowing(int32_t *out, @@ -92,82 +144,83 @@ atrac3_iqmf_matrixing: * in += 2; * out += 2; * } + * + * The window is symmetric, win[i] = win[47-i], so only its first half is + * read: win[2k] and win[2k+1] serve in[2k] and in[2k+1] from the front of + * the 48 inputs and, swapped, in[46-2k] and in[47-2k] from the back. That + * is 24 coefficient loads for an output pair where there were 48, and + * they can be loaded four at a time. The sums are kept in 64 bits, so the + * order of the additions does not change the result. + * * Note: r12 is a scratch register and can be used without restorage. ****************************************************************************/ -/* To be called as first block to call smull for initial filling of the result - * registers lr/r9 and r12/r8. */ -#define DEWIN_8_SAMPLES_MUL_ASM \ - ldmia r2!, {r4, r5}; /* load win[0..1] */ \ - ldmia r1!, {r6, r7}; /* load in [0..1] */ \ - smull lr , r9, r4, r6; /* s1 = win[0] * in[0] */ \ - smull r12, r8, r5, r7; /* s2 = win[1] * in[1] */ \ - ldmia r2!, {r4, r5}; /* load win[i..i+1] */ \ - ldmia r1!, {r6, r7}; /* load in [i..i+1] */ \ - smlal lr , r9, r4, r6; /* s1 = win[i ] * in[i ] */ \ - smlal r12, r8, r5, r7; /* s2 = win[i+1] * in[i+1] */ \ - ldmia r2!, {r4, r5}; /* load win[i..i+1] */ \ - ldmia r1!, {r6, r7}; /* load in [i..i+1] */ \ - smlal lr , r9, r4, r6; /* s1 = win[i ] * in[i ] */ \ - smlal r12, r8, r5, r7; /* s2 = win[i+1] * in[i+1] */ \ - ldmia r2!, {r4, r5}; /* load win[i..i+1] */ \ - ldmia r1!, {r6, r7}; /* load in [i..i+1] */ \ - smlal lr , r9, r4, r6; /* s1 = win[i ] * in[i ] */ \ - smlal r12, r8, r5, r7; /* s2 = win[i+1] * in[i+1] */ - -/* Called after first block. Will always multiply-add to the result registers - * lr/r9 and r12/r8. */ -#define DEWIN_8_SAMPLES_MLA_ASM \ - ldmia r2!, {r4, r5}; /* load win[i..i+1] */ \ - ldmia r1!, {r6, r7}; /* load in [i..i+1] */ \ - smlal lr , r9, r4, r6; /* s1 = win[i ] * in[i ] */ \ - smlal r12, r8, r5, r7; /* s2 = win[i+1] * in[i+1] */ \ - ldmia r2!, {r4, r5}; /* load win[i..i+1] */ \ - ldmia r1!, {r6, r7}; /* load in [i..i+1] */ \ - smlal lr , r9, r4, r6; /* s1 = win[i ] * in[i ] */ \ - smlal r12, r8, r5, r7; /* s2 = win[i+1] * in[i+1] */ \ - ldmia r2!, {r4, r5}; /* load win[i..i+1] */ \ - ldmia r1!, {r6, r7}; /* load in [i..i+1] */ \ - smlal lr , r9, r4, r6; /* s1 = win[i ] * in[i ] */ \ - smlal r12, r8, r5, r7; /* s2 = win[i+1] * in[i+1] */ \ - ldmia r2!, {r4, r5}; /* load win[i..i+1] */ \ - ldmia r1!, {r6, r7}; /* load in [i..i+1] */ \ - smlal lr , r9, r4, r6; /* s1 = win[i ] * in[i ] */ \ - smlal r12, r8, r5, r7; /* s2 = win[i+1] * in[i+1] */ +/* Eight taps: coefficients win[2k..2k+3], two input pairs from the front + * (r1) and two from the back (r3). s1 is lr/r9 and s2 is r12/r8. The + * samples are the last operand of the multiplies, as they are the smaller + * one and that is what the multiplier's early termination goes by. */ +#define DEWIN_8_SAMPLES_ASM(first1, first2) \ + ldmia r2!, {r4-r7}; /* load win[2k..2k+3] */ \ + ldmia r1!, {r10, r11}; /* load in[2k], in[2k+1] */ \ + first1 lr , r9, r4, r10; /* s1 += win[2k ] * in[2k ] */ \ + first2 r12, r8, r5, r11; /* s2 += win[2k+1] * in[2k+1] */ \ + ldmdb r3!, {r10, r11}; /* load in[46-2k], in[47-2k] */ \ + smlal lr , r9, r5, r10; /* s1 += win[2k+1] * in[46-2k] */ \ + smlal r12, r8, r4, r11; /* s2 += win[2k ] * in[47-2k] */ \ + ldmia r1!, {r10, r11}; /* load in[2k+2], in[2k+3] */ \ + smlal lr , r9, r6, r10; /* s1 += win[2k+2] * in[2k+2] */ \ + smlal r12, r8, r7, r11; /* s2 += win[2k+3] * in[2k+3] */ \ + ldmdb r3!, {r10, r11}; /* load in[44-2k], in[45-2k] */ \ + smlal lr , r9, r7, r10; /* s1 += win[2k+3] * in[44-2k] */ \ + smlal r12, r8, r6, r11; /* s2 += win[2k+2] * in[45-2k] */ +/* atrac3_iqmf_dewindowing is the function above. atrac3_iqmf_dewindowing_out + * is the same for the last iqmf stage, with the result scaled up by OUT_SHIFT + * bits, which then is the scale of the decoder's output: the bits are taken + * from the 64-bit sums, that have them. */ +.macro DEWINDOWING name, shift .align 2 - .global atrac3_iqmf_dewindowing - .type atrac3_iqmf_dewindowing, %function - -atrac3_iqmf_dewindowing: + .global \name + .type \name, %function + +\name: /* r0 = dest */ /* r1 = input samples */ /* r2 = window coefficients */ /* r3 = counter */ - stmfd sp!, {r4-r9, lr} /* save non-scratch registers */ + stmfd sp!, {r4-r11, lr} /* save non-scratch registers */ + add r3, r0, r3, lsl #3 /* end of dest: two words a count */ + stmfd sp!, {r3} + add r3, r1, #192 /* r3 = end of the 48 inputs */ -.iqmf_dewindow_outer_loop: /* outer loop 0...counter-1 */ +1: /* outer loop 0...counter-1 */ - DEWIN_8_SAMPLES_MUL_ASM /* 0.. 7, use "MUL" macro here! */ - DEWIN_8_SAMPLES_MLA_ASM /* 8..15 */ - DEWIN_8_SAMPLES_MLA_ASM /* 16..23 */ - DEWIN_8_SAMPLES_MLA_ASM /* 24..31 */ - DEWIN_8_SAMPLES_MLA_ASM /* 32..39 */ - DEWIN_8_SAMPLES_MLA_ASM /* 40..47 */ + DEWIN_8_SAMPLES_ASM(smull, smull) /* in[ 0.. 3], in[44..47] */ + DEWIN_8_SAMPLES_ASM(smlal, smlal) /* in[ 4.. 7], in[40..43] */ + DEWIN_8_SAMPLES_ASM(smlal, smlal) /* in[ 8..11], in[36..39] */ + DEWIN_8_SAMPLES_ASM(smlal, smlal) /* in[12..15], in[32..35] */ + DEWIN_8_SAMPLES_ASM(smlal, smlal) /* in[16..19], in[28..31] */ + DEWIN_8_SAMPLES_ASM(smlal, smlal) /* in[20..23], in[24..27] */ - mov lr , lr , lsr #31 - orr r9, lr , r9, lsl #1 /* s1 = low>>31 || hi<<1 */ - mov r12, r12, lsr #31 - orr r8, r12, r8, lsl #1 /* s2 = low>>31 || hi<<1 */ + mov lr , lr , lsr #31-\shift + orr r9, lr , r9, lsl #1+\shift /* s1 = low>>31 || hi<<1, << shift */ + mov r12, r12, lsr #31-\shift + orr r8, r12, r8, lsl #1+\shift /* s2 = low>>31 || hi<<1, << shift */ stmia r0!, {r8, r9} /* store result out[0]=s2, out[1]=s1 */ - sub r1, r1, #184 /* roll back 64 entries = 184 bytes */ - sub r2, r2, #192 /* roll back 48 entries = 192 bytes = win[0] */ + ldr r4, [sp] /* end of dest */ + sub r1, r1, #88 /* back 24 entries, on 2: in += 2 */ + add r3, r3, #104 /* on 24 entries, and 2 more */ + sub r2, r2, #96 /* roll back 24 entries = win[0] */ - subs r3, r3, #1 /* outer loop -= 1 */ - bgt .iqmf_dewindow_outer_loop + cmp r0, r4 + bne 1b - ldmpc regs=r4-r9 /* restore registers */ + add sp, sp, #4 + ldmpc regs=r4-r11 /* restore registers */ -.atrac3_iqmf_dewindowing_end: - .size atrac3_iqmf_dewindowing,.atrac3_iqmf_dewindowing_end-atrac3_iqmf_dewindowing + .size \name, .-\name +.endm + + DEWINDOWING atrac3_iqmf_dewindowing, 0 + DEWINDOWING atrac3_iqmf_dewindowing_out, OUT_SHIFT diff --git a/lib/rbcodec/codecs/libatrac/atrac3_armv5e.S b/lib/rbcodec/codecs/libatrac/atrac3_armv5e.S index d5ba92c55d..8bfaca2410 100644 --- a/lib/rbcodec/codecs/libatrac/atrac3_armv5e.S +++ b/lib/rbcodec/codecs/libatrac/atrac3_armv5e.S @@ -66,8 +66,13 @@ atrac3_iqmf_dewindowing_armv5e: /* 0.. 7 */ ldmia r2!, {r4, r5, r8, r9} /* load win[0..7] */ ldmia r1!, {r6, r7, r10, r11} /* load in[0..3] to avoid stall on arm11 */ - smulwb lr, r6, r4 /* s1 = in[0] * win[0] */ - smulwt r12, r7, r4 /* s2 = in[1] * win[1] */ + /* Each of the 24 products of a sum is rounded down by half a unit on + * average. Start the sums 12 up to make up for it; without that the + * output has a dc offset and, from the high band, a tone at fs/2. */ + mov lr, #12 + mov r12, #12 + smlawb lr, r6, r4, lr /* s1 += in[0] * win[0] >> 16 */ + smlawt r12, r7, r4, r12 /* s2 += in[1] * win[1] >> 16 */ smlawb lr, r10, r5, lr /* s1 += in[i ] * win[i ] >> 16 */ smlawt r12, r11,r5, r12 /* s2 += in[i+1] * win[i+1] >> 16 */ @@ -152,7 +157,7 @@ atrac3_iqmf_dewindowing_armv5e: mov r12, r12, lsl #1 stmia r0!, {r12, lr} /* store result out[0]=s2, out[1]=s1 */ - sub r1, r1, #184 /* roll back 64 entries = 184 bytes */ + sub r1, r1, #184 /* roll back 46 entries = 184 bytes */ sub r2, r2, #96 /* roll back 48 entries * 2 bytes = 96 bytes = win[0] */ subs r3, r3, #1 /* outer loop -= 1 */ diff --git a/lib/rbcodec/codecs/libatrac/atrac3data_fixed.h b/lib/rbcodec/codecs/libatrac/atrac3data_fixed.h index 3cc4e0837c..2bc1316547 100644 --- a/lib/rbcodec/codecs/libatrac/atrac3data_fixed.h +++ b/lib/rbcodec/codecs/libatrac/atrac3data_fixed.h @@ -76,6 +76,36 @@ static const int32_t window_lookup[128] ICONST_ATTR MEM_ALIGN_ATTR = { -0x82660c01, -0x80ca4a01, }; +/* The window above is the first 128 points of the 512 point mdct window. + * Points 128..255 are these values plus one, scaled by 2^31; they rise to + * 1.207 and fall back to 1. The second half of the window mirrors the + * first. In IRAM only where there is room: with it the codec does not fit + * the 48KB of the smaller targets. */ +static const int32_t window_lookup_mid[128] ICONST_ATTR_LARGE_IRAM MEM_ALIGN_ATTR = { + 0x00c7d1c8, 0x024fd962, 0x03cd6738, 0x05401bc8, 0x06a79e95, 0x08039e75, + 0x0953d1d1, 0x0a97f6df, 0x0bcfd3c6, 0x0cfb36c3, 0x0e19f636, 0x0f2bf0a8, + 0x10310cc9, 0x11293955, 0x12146d01, 0x12f2a64e, 0x13c3eb59, 0x148849a0, + 0x153fd5c0, 0x15eaab2c, 0x1688ebd9, 0x171abfeb, 0x17a05559, 0x1819df8c, + 0x188796ff, 0x18e9b8db, 0x1940868e, 0x198c456d, 0x19cd3e43, 0x1a03bcf6, + 0x1a301021, 0x1a5288af, 0x1a6b7984, 0x1a7b371a, 0x1a821730, 0x1a807072, + 0x1a769a2c, 0x1a64ebfc, 0x1a4bbd8d, 0x1a2b6653, 0x1a043d52, 0x19d698de, + 0x19a2ce6d, 0x19693261, 0x192a17e5, 0x18e5d0be, 0x189cad2d, 0x184efbcf, + 0x17fd0985, 0x17a72158, 0x174d8c6b, 0x16f091e8, 0x169076f2, 0x162d7ea1, + 0x15c7e9f4, 0x155ff7cf, 0x14f5e4fc, 0x1489ec23, 0x141c45d4, 0x13ad2886, + 0x133cc89d, 0x12cb5870, 0x12590851, 0x11e60697, 0x11727fa4, 0x10fe9df7, + 0x108a8a2e, 0x10166b1a, 0x0fa265c6, 0x0f2e9d86, 0x0ebb3404, 0x0e48494e, + 0x0dd5fbe2, 0x0d6468bc, 0x0cf3ab63, 0x0c83ddf9, 0x0c151945, 0x0ba774c2, + 0x0b3b06af, 0x0acfe414, 0x0a6620d7, 0x09fdcfc5, 0x0997029e, 0x0931ca20, + 0x08ce3615, 0x086c555d, 0x080c35f8, 0x07ade513, 0x07516f11, 0x06f6df92, + 0x069e4182, 0x06479f20, 0x05f30203, 0x05a0732a, 0x054ffafe, 0x0501a15d, + 0x04b56d9f, 0x046b66a1, 0x042392c9, 0x03ddf80e, 0x039a9bfc, 0x035983bd, + 0x031ab41f, 0x02de3196, 0x02a40042, 0x026c23f9, 0x0236a043, 0x02037867, + 0x01d2af6a, 0x01a44813, 0x017844f1, 0x014ea85c, 0x0127747d, 0x0102ab49, + 0x00e04e8c, 0x00c05fe5, 0x00a2e0cf, 0x0087d29c, 0x006f367d, 0x00590d81, + 0x00455897, 0x00341890, 0x00254e20, 0x0018f9df, 0x000f1c4a, 0x0007b5c4, + 0x0002c697, 0x00004ef5, +}; + /* Gain tables scaled by 2^16 */ static const int32_t gain_tab2[31] ICONST_ATTR = { 0x0003ab03, 0x00035d14, 0x0003159d, 0x0002d414, 0x000297fb, 0x000260e0, diff --git a/lib/rbcodec/codecs/libffmpegFLAC/arm.S b/lib/rbcodec/codecs/libffmpegFLAC/arm.S index 8c4e810da8..ef072b9093 100644 --- a/lib/rbcodec/codecs/libffmpegFLAC/arm.S +++ b/lib/rbcodec/codecs/libffmpegFLAC/arm.S @@ -270,3 +270,541 @@ lpc_decode_arm: .exit: ldmpc regs=r4-r11 + +/* --------------------------------------------------------------------------- + The 64-bit LPC filter. + + void flac_lpc_32_arm(int32_t *decoded, int coeffs[], int pred_order, + int qlevel, int len); + + Same contract as flac_lpc_32_c in decoder.c, which decode_subframe_lpc uses + whenever bps + coeff_prec + av_log2(pred_order) exceeds 32 -- every 24-bit + stream, and any 16-bit stream encoded at high coefficient precision: + + for (i = pred_order; i < len; i++, decoded++) { + int64_t sum = 0; + for (j = 0; j < pred_order; j++) + sum += (int64_t)coeffs[pred_order-j-1] * decoded[j]; + decoded[j] += sum >> qlevel; + } + + The oldest history sample multiplies the highest-numbered coefficient, the + same convention lpc_decode_arm above uses. + + Orders 1..8 get their own loop with every coefficient held in a register: + the C loop reloads all of them for every output sample although they are + invariant for the whole call, and spills the loop bound to the stack on top + of that. Orders above 8 go through a chunked loop; the reference encoder's + default maximum order is 8 and no test stream here exceeds it. + + Registers, all orders: + + r0 history pointer, walks on one sample per output + r1:r2 64-bit accumulator (r1 low); on entry coeffs and pred_order + r3, lr history samples, and scratch in the tail + r12 ((outputs remaining - 1) << 5) | qlevel + r4... coefficients, ascending: r4 = coeffs[0] + ...r11 further coefficients, then spare history samples + + Packing the output counter and qlevel into one register is what makes order + 8 fit: eight coefficients, a 64-bit accumulator, a pointer and the counter + are already thirteen of the fourteen usable registers. Unpacking costs one + AND per output sample and buys a second sample register, which is what keeps + every load two instructions clear of the multiply that consumes it -- worth + a cycle per tap on both cores. + + Multiply operand order is deliberate. On ARM7TDMI the multiplier walks Rs + eight bits at a time and stops once the rest of Rs is all sign, so the + coefficient -- fifteen bits or fewer by construction, since coeff_prec is + capped at 15 -- goes in Rs and the sample in Rm. That is two passes rather + than four whatever the sample width, which is why a 24-bit stream pays no + more per multiply here than a 16-bit one. + */ + +/* Placement. The hot half of this filter wants IRAM on PP502x, but the + codec's 80 KB window there is nearly full before it arrives: FLAC already + keeps four 18 KB sample buffers in it. So the orders that are a fraction + of a percent of any real stream, and the generic loop that covers the rest, + are left in DRAM -- a dense loop like this is cache-resident after its first + pass anyway. Define FLAC_LPC32_NO_IRAM to demote the whole thing on a + target that needs the space back. */ +#if defined(USE_IRAM) && !defined(FLAC_LPC32_NO_IRAM) +#define LPC32_HOT .section .icode,"ax",%progbits +#else +#define LPC32_HOT .text +#endif +#define LPC32_COLD .text + + /* Shift the accumulator down by qlevel, add the residual waiting at + p[order], store it back, and step the window on by one sample. The + loads above leave r0 at p[order], so the store's post-index rewinds it. + A register-specified shift by 32 yields zero on ARM, which is exactly + what the qlevel == 0 case needs from the high word. */ + .macro LPCTAIL order + ldr r3, [r0] @ residual + and lr, r12, #31 @ qlevel + mov r1, r1, lsr lr + rsb lr, lr, #32 + orr r1, r1, r2, lsl lr + add r1, r3, r1 + str r1, [r0], #-(4 * \order - 4) + subs r12, r12, #32 @ one output done; the borrow out of the + .endm @ count field is the loop's exit test + + /* The high-order tail. Orders above 8 cannot hold their coefficients, so + those loops keep the accumulator in r10:r11 and have r2 free for a + fourth history sample -- the order is a constant there, so nothing + needs to remember it. */ + .macro HTAIL order + ldr r2, [r0] @ residual + and lr, r12, #31 @ qlevel + mov r10, r10, lsr lr + rsb lr, lr, #32 + orr r10, r10, r11, lsl lr + add r10, r2, r10 + str r10, [r0], #-(4 * \order - 4) + subs r12, r12, #32 + .endm + + /* One pass of four taps, for the orders that cannot keep their + coefficients in registers. Both operand streams burst: coefficients + descend from the top, history ascends. r2 is the fourth sample + register, which is what lets the samples come in one ldm of four + rather than three plus a single load. */ + .macro HCHUNK first + ldmdb r3!, { r4-r7 } + ldmia r0!, { r2, r8, r9, lr } + .ifnb \first + smull r10, r11, r2, r7 @ opens the accumulator, so it never + .else @ has to be zeroed + smlal r10, r11, r2, r7 + .endif + smlal r10, r11, r8, r6 + smlal r10, r11, r9, r5 + smlal r10, r11, lr, r4 + .endm + + LPC32_HOT + .align 2 + .global flac_lpc_32_arm +flac_lpc_32_arm: + stmfd sp!, { r4-r11, lr } + ldr r12, [sp, #36] @ len + subs r12, r12, r2 @ outputs = len - pred_order + ble .Lx_ret + sub r12, r12, #1 + orr r12, r3, r12, lsl #5 +.Lx_entry: @ internal: r0, r1, r2 and r12 set up + cmp r2, #12 + addls pc, pc, r2, lsl #2 + ldr pc, .Lx_pgen @ 13..32: the generic loop, in DRAM +@ jumptable: + b .Lx_ret @ order 0 cannot occur + ldr pc, .Lx_p1 @ 1..3 are a fraction of a percent of + ldr pc, .Lx_p2 @ any stream and the tail dominates them + ldr pc, .Lx_p3 @ anyway, so they sit in DRAM + b .Lx_order4 + b .Lx_order5 + b .Lx_order6 + b .Lx_order7 + b .Lx_order8 + ldr pc, .Lx_pgen @ 9 -- generic loop + b .Lx_order10 + ldr pc, .Lx_pgen @ 11 -- generic loop +@ order 12 is the last table slot, so it falls through. 10 and 12 get their +@ own loops because they are what the reference encoder's -8 actually emits; +@ 9 and 11 would too if there were IRAM to put them in. +.Lx_order12: + add r3, r1, #48 @ coefficient pointer, descending +.Lx_loop12: + HCHUNK first + HCHUNK + HCHUNK + HTAIL 12 + bpl .Lx_order12 + ldmpc regs=r4-r11 + +.Lx_order10: + add r3, r1, #40 +.Lx_loop10: + ldmdb r3!, { r4, r5 } @ coeffs[8..9], the two odd taps + ldmia r0!, { r2, r8 } @ p[0..1] + smull r10, r11, r2, r5 + smlal r10, r11, r8, r4 + HCHUNK + HCHUNK + HTAIL 10 + bpl .Lx_order10 + ldmpc regs=r4-r11 +@ order 8 is reached from the table above +.Lx_order8: + ldmia r1, { r4-r11 } @ coeffs[0..7], resident for the call +.Lx_loop8: + ldmia r0!, { r3, lr } @ p[0..1]. Eight resident coefficients + smull r1, r2, r3, r11 @ leave room for two samples, and a pair + smlal r1, r2, lr, r10 @ is still worth bursting: an ldm of n is + ldmia r0!, { r3, lr } @ n+1 cycles against 2n for separate + smlal r1, r2, r3, r9 @ loads, so two words cost 3 rather than + smlal r1, r2, lr, r8 @ 4. The transfer writes r3 before lr, + ldmia r0!, { r3, lr } @ so consuming r3 first both avoids the + smlal r1, r2, r3, r7 @ stall and earns back the final I cycle. + smlal r1, r2, lr, r6 + ldmia r0!, { r3, lr } @ p[6..7] + smlal r1, r2, r3, r5 + smlal r1, r2, lr, r4 + LPCTAIL 8 + bpl .Lx_loop8 + ldmpc regs=r4-r11 + +.Lx_order7: + ldmia r1, { r4-r10 } @ coeffs[0..6] +.Lx_loop7: + ldmia r0!, { r3, r11, lr } @ p[0..2]: 3+2 cycles against 3x2 for + smull r1, r2, r3, r10 @ single loads on ARM7TDMI + smlal r1, r2, r11, r9 + smlal r1, r2, lr, r8 + ldmia r0!, { r3, r11, lr } @ p[3..5] + smlal r1, r2, r3, r7 @ r3 lands first, lr last, so reading r3 + smlal r1, r2, r11, r6 @ straight after the ldm cannot stall + ldr r3, [r0], #4 @ p[6] + smlal r1, r2, lr, r5 + smlal r1, r2, r3, r4 + LPCTAIL 7 + bpl .Lx_loop7 + ldmpc regs=r4-r11 + +.Lx_order6: + ldmia r1, { r4-r9 } @ coeffs[0..5] +.Lx_loop6: + ldmia r0!, { r3, r10, r11, lr } @ p[0..3]: 4+2 against 4x2 + smull r1, r2, r3, r9 + smlal r1, r2, r10, r8 + smlal r1, r2, r11, r7 + ldmia r0!, { r3, r10 } @ p[4..5] + smlal r1, r2, lr, r6 @ lr is still the first ldm's + smlal r1, r2, r3, r5 + smlal r1, r2, r10, r4 + LPCTAIL 6 + bpl .Lx_loop6 + ldmpc regs=r4-r11 + +.Lx_order5: + ldmia r1, { r4-r8 } @ coeffs[0..4] +.Lx_loop5: + ldmia r0!, { r3, r9, r10, r11, lr } @ the whole window in 5+2 cycles + smull r1, r2, r3, r8 + smlal r1, r2, r9, r7 + smlal r1, r2, r10, r6 + smlal r1, r2, r11, r5 + smlal r1, r2, lr, r4 + LPCTAIL 5 + bpl .Lx_loop5 + ldmpc regs=r4-r11 + +.Lx_order4: + ldmia r1, { r4-r7 } @ coeffs[0..3] +.Lx_loop4: + ldmia r0!, { r3, r8, r9, lr } + smull r1, r2, r3, r7 + smlal r1, r2, r8, r6 + smlal r1, r2, r9, r5 + smlal r1, r2, lr, r4 + LPCTAIL 4 + bpl .Lx_loop4 + ldmpc regs=r4-r11 + + LPC32_COLD + .align 2 +.Lx_order3: + ldmia r1, { r4-r6 } @ coeffs[0..2] +.Lx_loop3: + ldmia r0!, { r3, r8, lr } + smull r1, r2, r3, r6 + smlal r1, r2, r8, r5 + smlal r1, r2, lr, r4 + LPCTAIL 3 + bpl .Lx_loop3 + ldmpc regs=r4-r11 + +.Lx_order2: + ldmia r1, { r4-r5 } @ coeffs[0..1] +.Lx_loop2: + ldmia r0!, { r3, lr } + smull r1, r2, r3, r5 + smlal r1, r2, lr, r4 + LPCTAIL 2 + bpl .Lx_loop2 + ldmpc regs=r4-r11 + + /* Order 1 carries the sample it just reconstructed into the next + iteration in r3: it is the only history the next output needs, so the + loop holds one load and one multiply and never stalls. */ +.Lx_order1: + ldr r4, [r1] @ the one coefficient + ldr r3, [r0], #4 @ p[0] +.Lx_loop1: + smull r1, r2, r3, r4 + ldr r3, [r0] @ residual + and lr, r12, #31 + mov r1, r1, lsr lr + rsb lr, lr, #32 + orr r1, r1, r2, lsl lr + add r3, r3, r1 @ and this is the next p[0] + str r3, [r0], #4 + subs r12, r12, #32 + bpl .Lx_loop1 + ldmpc regs=r4-r11 + + /* Orders 9..32. Four taps a pass, both operand streams bursted: the + coefficients descend from coeffs + order and the history ascends, so the + inner loop ends when the coefficient pointer reaches coeffs and needs no + counter of its own. order & 3 leftover taps go first, one at a time. */ +.Lx_highgen: @ orders 9, 11 and 13..32 +.Lx_dnext: + add r3, r1, r2, lsl #2 @ coefficient pointer, descending + mov r10, #0 @ 64-bit accumulator (r10 low) + mov r11, #0 + ands lr, r2, #3 + beq .Lx_dchunk +.Lx_drem: + ldr r8, [r0], #4 + ldr r9, [r3, #-4]! + subs lr, lr, #1 + smlal r10, r11, r8, r9 + bne .Lx_drem +.Lx_dchunk: + ldmdb r3!, { r4-r7 } @ four coefficients, descending + ldmia r0!, { r8, r9, lr } @ three samples, and the fourth below, + smlal r10, r11, r8, r7 @ because r8 is free again by then + ldr r8, [r0], #4 + smlal r10, r11, r9, r6 + smlal r10, r11, lr, r5 + smlal r10, r11, r8, r4 + cmp r3, r1 + bhi .Lx_dchunk + ldr r3, [r0] @ residual, r0 is at p[order] + and lr, r12, #31 + mov r10, r10, lsr lr + rsb lr, lr, #32 + orr r10, r10, r11, lsl lr + add r10, r3, r10 + str r10, [r0] + sub r0, r0, r2, lsl #2 @ back to p[0]... + add r0, r0, #4 @ ...and on one sample + subs r12, r12, #32 + bpl .Lx_dnext + ldmpc regs=r4-r11 + + LPC32_HOT +.Lx_ret: + ldmpc regs=r4-r11 +.Lx_p1: .word .Lx_order1 +.Lx_p2: .word .Lx_order2 +.Lx_p3: .word .Lx_order3 +.Lx_pgen: .word .Lx_highgen + +#if ARM_ARCH >= 5 +/* --------------------------------------------------------------------------- + The same filter for ARMv5E, using the packed 16-bit multiplies. + + void flac_lpc_32_arm_narrow(int32_t *decoded, int coeffs[], int pred_order, + int qlevel, int len); + + Only valid where every history sample fits a signed halfword, which is what + bps <= 16 guarantees on entry: the warm-up samples come from get_sbits(bps), + and every sample this loop reconstructs is checked before the next output + reads it. The caller selects it on bps; it falls back to the 32-bit kernel + above by itself whenever a precondition fails, so it is never the reason an + answer differs. + + Why this needs its own kernel, and why ARMv4 gets none: + + smlal is 32x32 -> 64 and costs three cycles on ARM926. smlabb is + 16x16 + 32 -> 32 and costs one. Two things have to hold before the cheap + one is exact. The sample must fit sixteen bits -- true only for bps <= 16, + which is why a 24-bit stream cannot use this and stays on smlal. And the + sum must fit 32 bits, which holds iff sum|coeffs| * 32768 < 2^31, i.e. + sum|coeffs| <= 65535. That is a property of the subframe's coefficients, + so it is tested once per call, not per sample. Measured over the test + corpus it holds for 94% of the 16-bit high-precision subframes. + + Splitting a wider sample into halves was measured and rejected: two + smlalbb plus the extraction is five cycles against smlal's three, so for + bps > 16 the 32-bit multiply is not merely adequate, it is optimal. + + Registers: + + r0 history pointer r1 coeffs, kept for the fallback + r2 32-bit accumulator r3 scratch + r4..r7 coefficients, packed two to a register, low half first + r8..r11, lr history samples r12 packed count and qlevel + */ + + /* Pack coeffs[2i] and coeffs[2i+1] into one register, low index in the + low half, so that smlabb selects the even coefficient and smlabt the + odd one. Three instructions once per call, not per sample. */ + .macro PACK2 dst, even, odd + mov \dst, \even, lsl #16 + mov \dst, \dst, lsr #16 + orr \dst, \dst, \odd, lsl #16 + .endm + + /* Accumulator down by qlevel, plus the residual. One register-specified + shift folded into the add, where the 64-bit kernel needs a shift, a + reverse subtract and an or. */ + .macro NTAIL order + ldr r3, [r0] @ residual + and lr, r12, #31 @ qlevel + add r2, r3, r2, asr lr + str r2, [r0], #-(4 * \order - 4) + .endm + + /* Does the sample just reconstructed still fit a signed halfword? If not, + the remaining outputs go through the 32-bit kernel. Three cycles per + output sample, against the eight per output that the cheap multiplies + save at order 8. */ + .macro NGUARD order + add r3, r2, #0x8000 + cmp r3, #0x10000 + bcs .Ln_bail\order + .endm + + .macro NBAIL order +.Ln_bail\order: + subs r12, r12, #32 @ that output was finished + ldmpc cond=mi, regs=r4-r11 @ and it was the last one + mov r2, #\order + b .Lx_entry + .endm + + .align 2 + .global flac_lpc_32_arm_narrow +flac_lpc_32_arm_narrow: + stmfd sp!, { r4-r11, lr } + ldr r12, [sp, #36] @ len + subs r12, r12, r2 @ outputs = len - pred_order + ble .Ln_ret + sub r12, r12, #1 + orr r12, r3, r12, lsl #5 + @ sum|coeffs| <= 65535, or the 32-bit accumulator could overflow + mov r3, #0 + sub lr, r2, #1 +.Ln_sum: + ldr r4, [r1, lr, lsl #2] @ walks coeffs[order-1] down to coeffs[0] + cmp r4, #0 + rsblt r4, r4, #0 + add r3, r3, r4 + subs lr, lr, #1 + bge .Ln_sum + cmp r3, #0x10000 + bcs .Lx_entry @ too large: use the 32-bit kernel + cmp r2, #8 + addls pc, pc, r2, lsl #2 + b .Lx_entry @ orders 9 and up: 32-bit kernel +@ jumptable: + b .Ln_ret @ order 0 cannot occur + b .Lx_entry @ orders 1..4 are left to the 32-bit + b .Lx_entry @ kernel: they are absent from every + b .Lx_entry @ stream that reaches this path, and the + b .Lx_entry @ tail dominates them anyway + b .Ln_order5 + b .Ln_order6 + b .Ln_order7 +@ order 8 falls through +.Ln_order8: + ldmia r1, { r8, r9, r10, r11 } @ coeffs[0..3] + PACK2 r4, r8, r9 + PACK2 r5, r10, r11 + add lr, r1, #16 + ldmia lr, { r8, r9, r10, r11 } @ coeffs[4..7] + PACK2 r6, r8, r9 + PACK2 r7, r10, r11 +.Ln_loop8: + ldmia r0!, { r8, r9, r10, r11 } @ p[0..3] + smulbt r2, r8, r7 @ p[0] * coeffs[7]: one cycle, and it + smlabb r2, r9, r7, r2 @ needs no accumulator to be zeroed + smlabt r2, r10, r6, r2 + smlabb r2, r11, r6, r2 + ldmia r0!, { r8, r9, r10, r11 } @ p[4..7] + smlabt r2, r8, r5, r2 + smlabb r2, r9, r5, r2 + smlabt r2, r10, r4, r2 + smlabb r2, r11, r4, r2 + NTAIL 8 + NGUARD 8 + subs r12, r12, #32 + bpl .Ln_loop8 + ldmpc regs=r4-r11 + NBAIL 8 + +.Ln_order7: + ldmia r1, { r8, r9, r10, r11 } @ coeffs[0..3] + PACK2 r4, r8, r9 + PACK2 r5, r10, r11 + add lr, r1, #16 + ldmia lr, { r8, r9, r10 } @ coeffs[4..6] + PACK2 r6, r8, r9 + mov r7, r10 @ coeffs[6] on its own, low half +.Ln_loop7: + ldmia r0!, { r8, r9, r10, r11 } @ p[0..3] + smulbb r2, r8, r7 @ p[0] * coeffs[6] + smlabt r2, r9, r6, r2 + smlabb r2, r10, r6, r2 + smlabt r2, r11, r5, r2 + ldmia r0!, { r8, r9, r10 } @ p[4..6] + smlabb r2, r8, r5, r2 + smlabt r2, r9, r4, r2 + smlabb r2, r10, r4, r2 + NTAIL 7 + NGUARD 7 + subs r12, r12, #32 + bpl .Ln_loop7 + ldmpc regs=r4-r11 + NBAIL 7 + +.Ln_order6: + ldmia r1, { r8, r9, r10, r11 } @ coeffs[0..3] + PACK2 r4, r8, r9 + PACK2 r5, r10, r11 + add lr, r1, #16 + ldmia lr, { r8, r9 } @ coeffs[4..5] + PACK2 r6, r8, r9 +.Ln_loop6: + ldmia r0!, { r8, r9, r10, r11 } @ p[0..3] + smulbt r2, r8, r6 @ p[0] * coeffs[5] + smlabb r2, r9, r6, r2 + smlabt r2, r10, r5, r2 + smlabb r2, r11, r5, r2 + ldmia r0!, { r8, r9 } @ p[4..5] + smlabt r2, r8, r4, r2 + smlabb r2, r9, r4, r2 + NTAIL 6 + NGUARD 6 + subs r12, r12, #32 + bpl .Ln_loop6 + ldmpc regs=r4-r11 + NBAIL 6 + +.Ln_order5: + ldmia r1, { r8, r9, r10, r11 } @ coeffs[0..3] + PACK2 r4, r8, r9 + PACK2 r5, r10, r11 + ldr r6, [r1, #16] @ coeffs[4] on its own, low half +.Ln_loop5: + ldmia r0!, { r8, r9, r10, r11 } @ p[0..3] + ldr lr, [r0], #4 @ p[4], loaded well clear of its multiply + smulbb r2, r8, r6 @ p[0] * coeffs[4] + smlabt r2, r9, r5, r2 + smlabb r2, r10, r5, r2 + smlabt r2, r11, r4, r2 + smlabb r2, lr, r4, r2 + NTAIL 5 + NGUARD 5 + subs r12, r12, #32 + bpl .Ln_loop5 + ldmpc regs=r4-r11 + NBAIL 5 + +.Ln_ret: + ldmpc regs=r4-r11 +#endif /* ARM_ARCH >= 5 */ diff --git a/lib/rbcodec/codecs/libffmpegFLAC/arm.h b/lib/rbcodec/codecs/libffmpegFLAC/arm.h index 39080d7f75..df3bae5611 100644 --- a/lib/rbcodec/codecs/libffmpegFLAC/arm.h +++ b/lib/rbcodec/codecs/libffmpegFLAC/arm.h @@ -5,4 +5,13 @@ void lpc_decode_arm(int blocksize, int qlevel, int pred_order, int32_t* data, int* coeffs); +/* The 64-bit LPC filter, the wide path of decode_subframe_lpc. Both have the + same contract as flac_lpc_32_c in decoder.c. The _narrow form uses the + ARMv5E packed 16-bit multiplies and is only valid when every history sample + fits a signed halfword, which bps <= 16 guarantees. */ +void flac_lpc_32_arm(int32_t *decoded, int coeffs[], int pred_order, + int qlevel, int len); +void flac_lpc_32_arm_narrow(int32_t *decoded, int coeffs[], int pred_order, + int qlevel, int len); + #endif diff --git a/lib/rbcodec/codecs/libffmpegFLAC/decoder.c b/lib/rbcodec/codecs/libffmpegFLAC/decoder.c index 7c3e0a4e96..ef81a89d8b 100644 --- a/lib/rbcodec/codecs/libffmpegFLAC/decoder.c +++ b/lib/rbcodec/codecs/libffmpegFLAC/decoder.c @@ -161,10 +161,12 @@ static int decode_residuals(FLACContext *s, int32_t *decoded, int pred_order) tmp = get_bits(&gb, rice_bits); if (tmp == rice_esc) { tmp = get_bits(&gb, 5); + /* An escape width of 0 means all residuals are zero. */ for (; i < samples; i++) - *decoded++ = get_sbits(&gb, tmp); + *decoded++ = tmp ? get_sbits(&gb, tmp) : 0; } else { - int real_limit = tmp ? (INT_MAX >> tmp) + 2 : INT_MAX; + unsigned lim = tmp ? (UINT_MAX >> tmp) + 2 : INT_MAX; + int real_limit = lim > INT_MAX ? INT_MAX : lim; for (; i < samples; i++) { int v = get_sr_golomb_flac(&gb, tmp, real_limit, 0); if ((unsigned) v == 0x80000000){ @@ -236,8 +238,16 @@ int decode_subframe_fixed(FLACContext *s, int32_t* decoded, int pred_order, int } #if !defined(CPU_COLDFIRE) +/* The assembler kernels in arm.S implement flac_lpc_32_c exactly; keep the C + version compiled anyway when they are in use, so that a build can A/B the + two by defining FLAC_NO_LPC32_ASM. */ +#if defined(CPU_ARM_CLASSIC) && !defined(FLAC_NO_LPC32_ASM) +#define FLAC_LPC32_ASM +#endif + static void flac_lpc_32_c(int32_t *decoded, int coeffs[], - int pred_order, int qlevel, int len) ICODE_ATTR_FLAC; + int pred_order, int qlevel, int len) + ICODE_ATTR_FLAC UNUSED_ATTR; static void flac_lpc_32_c(int32_t *decoded, int coeffs[], int pred_order, int qlevel, int len) { @@ -338,7 +348,20 @@ static int decode_subframe_lpc(FLACContext *s, int32_t* decoded, int pred_order, lpc_decode_emac_wide(s->blocksize - pred_order, qlevel, pred_order, decoded + pred_order, coeffs); #else + #if defined(FLAC_LPC32_ASM) + /* The 16-bit multiply kernel needs every history sample to fit a + signed halfword, which bps <= 16 guarantees, and checks its other + precondition itself. */ + #if (ARM_ARCH >= 5) && defined(FLAC_LPC32_NARROW_ASM) + if (bps <= 16) + flac_lpc_32_arm_narrow(decoded, coeffs, pred_order, qlevel, + s->blocksize); + else + #endif + flac_lpc_32_arm(decoded, coeffs, pred_order, qlevel, s->blocksize); + #else flac_lpc_32_c(decoded, coeffs, pred_order, qlevel, s->blocksize); + #endif if (bps <= 16) lpc_analyze_remodulate(decoded, coeffs, pred_order, qlevel, s->blocksize, bps); diff --git a/lib/rbcodec/codecs/libffmpegFLAC/golomb.h b/lib/rbcodec/codecs/libffmpegFLAC/golomb.h index 68d2558e1d..b62051ff1a 100644 --- a/lib/rbcodec/codecs/libffmpegFLAC/golomb.h +++ b/lib/rbcodec/codecs/libffmpegFLAC/golomb.h @@ -85,7 +85,7 @@ static inline int get_ur_golomb_jpegls(GetBitContext *gb, int k, int limit, buf = 0; } - buf += ((int32_t)i << k); + buf += ((unsigned int)i << k); } else if (i == limit - 1) { buf = SHOW_UBITS(re, gb, esc_len); LAST_SKIP_BITS(re, gb, esc_len); @@ -103,8 +103,9 @@ static inline int get_ur_golomb_jpegls(GetBitContext *gb, int k, int limit, * read signed golomb rice code (flac). */ static inline int get_sr_golomb_flac(GetBitContext *gb, int k, int limit, int esc_len){ - int v= get_ur_golomb_jpegls(gb, k, limit, esc_len); - return (v>>1) ^ -(v&1); + /* The folded value can use all 32 bits, so unfold it as unsigned. */ + unsigned int v= get_ur_golomb_jpegls(gb, k, limit, esc_len); + return (int)((v>>1) ^ -(v&1)); } /** diff --git a/lib/rbcodec/codecs/libm4a/demux.c b/lib/rbcodec/codecs/libm4a/demux.c index f9203a0893..869f23535f 100644 --- a/lib/rbcodec/codecs/libm4a/demux.c +++ b/lib/rbcodec/codecs/libm4a/demux.c @@ -478,7 +478,11 @@ static bool read_chunk_stco(qtmovie_t *qtmovie, size_t chunk_len) else { // we failed to alloc memory for lookup table, so reduce seek accuracy and try again - fit_numentries = numentries / accuracy_divider; + /* Entries 0, divider, 2*divider, ... require a ceiling divide. + * The old floor allocation overran by one entry whenever the + * chunk count was not evenly divisible. */ + fit_numentries = + (numentries + accuracy_divider - 1) / accuracy_divider; } } DEBUGF("lookup_table numentries %d, fit_numentries %ld\n", numentries, fit_numentries); @@ -638,18 +642,26 @@ static bool read_chunk_minf(qtmovie_t *qtmovie, size_t chunk_len) size_t size_remaining = chunk_len - 8; uint32_t i; - /* Check for smhd, only kind of minf we care about */ - - if ((i = stream_read_uint32(qtmovie->stream)) != 16) + /* Check for smhd, the only kind of minf we care about. MP4 video files + * normally put a video trak before the AAC trak. Older Rockbox code + * treated that vmhd as fatal, which made aac.codec unable to decode the + * audio from an otherwise ordinary .mp4/.m4v. Skip a non-audio minf and + * continue looking for the sound track. */ + i = stream_read_uint32(qtmovie->stream); + if (i < 8 || i > size_remaining) { DEBUGF("unexpected size in media info: %ld\n", (long)i); - stream_skip(qtmovie->stream, size_remaining-4); - return true; + return false; } if (stream_read_uint32(qtmovie->stream) != MAKEFOURCC('s','m','h','d')) { - DEBUGF("not a sound header! can't handle this.\n"); + stream_skip(qtmovie->stream, size_remaining - 8); + return true; + } + if (i != 16) + { + DEBUGF("unexpected sound header size: %ld\n", (long)i); return false; } diff --git a/lib/rbcodec/codecs/libopus/README.rockbox b/lib/rbcodec/codecs/libopus/README.rockbox index dd0ce70795..5d27580e9f 100644 --- a/lib/rbcodec/codecs/libopus/README.rockbox +++ b/lib/rbcodec/codecs/libopus/README.rockbox @@ -13,7 +13,20 @@ Opus: Celt: * changed #if FIXED_POINT to #ifdef FIXED_POINT in bands.c * changed #elif OPUS_ARM_INLINE_EDSP to #elif defined (OPUS_ARM_INLINE_EDSP) +* renamed OPUS_ARM_INLINE_ASM to OPUS_ARM_ASM_ARMV4_ONLY and + OPUS_ARM_INLINE_EDSP to OPUS_ARM_ASM_ARMV5E_AND_LATER throughout celt and + silk, since config.h defines exactly one of them per core * add #define ABS(a)(((a) < 0) ? - (a) :(a)) to mathops.h +* celt_decode_lost() in celt_decoder.c filters the PLC excitation in place + (celt_fir_inplace) instead of through a copy, to fit the codec stack + +Silk: +* added fixed-phase interpolation paths for the 8, 12 and 16 kHz to 48 kHz + steps in resampler_private_IIR_FIR.c (disable with OPUS_NO_SILK_FIXED_PHASE) +* resampler_private_IIR_FIR.c calls ARM kernels for those three steps: + silk/arm/resampler_armv4_asm.S and resampler_armv5e_asm.S, generated by + gen_resampler_armv4.py and gen_resampler_armv5e.py beside them (disable with + OPUS_ARM_NO_SILK_ASM); on ARMv5E and later the FIR buffer is word aligned Opus-tools: * copied src/opus_header.h and src/opus_header.c to lib/rbcodec/codecs/libopus diff --git a/lib/rbcodec/codecs/libopus/SOURCES b/lib/rbcodec/codecs/libopus/SOURCES index 53806545c7..379f700cbe 100644 --- a/lib/rbcodec/codecs/libopus/SOURCES +++ b/lib/rbcodec/codecs/libopus/SOURCES @@ -8,9 +8,32 @@ celt/entcode.c celt/entdec.c celt/entenc.c celt/kiss_fft.c +#if defined(CPU_ARM) && (ARM_ARCH == 4) +celt/arm/kiss_fft_armv4_asm.S +celt/arm/mdct_armv4_asm.S +celt/arm/pfa_armv4_asm.S +celt/arm/comb_filter_armv4_asm.S +celt/arm/denorm_armv4_asm.S +celt/arm/deemph_armv4_asm.S +celt/arm/exp_rotation1_armv4_asm.S +silk/arm/resampler_armv4_asm.S +#elif defined(CPU_ARM) && (ARM_ARCH >= 5) && (ARCH_PROFILE != ARM_PROFILE_MICRO) +celt/arm/kiss_fft_armv5e_asm.S +celt/arm/mdct_armv5e_asm.S +celt/arm/pfa_armv5e_asm.S +celt/arm/pitch_armv5e_asm.S +celt/arm/comb_filter_armv5e_asm.S +celt/arm/denorm_armv5e_asm.S +celt/arm/exp_rotation1_armv5e_asm.S +celt/arm/haar1_armv5e_asm.S +celt/arm/normres_armv5e_asm.S +celt/arm/deemph_armv5e_asm.S +silk/arm/resampler_armv5e_asm.S +#endif celt/laplace.c celt/mathops.c celt/mdct.c +celt/pfa.c celt/modes.c celt/pitch.c celt/quant_bands.c diff --git a/lib/rbcodec/codecs/libopus/celt/_kiss_fft_guts.h b/lib/rbcodec/codecs/libopus/celt/_kiss_fft_guts.h index ea847ff31d..f5794a4bff 100644 --- a/lib/rbcodec/codecs/libopus/celt/_kiss_fft_guts.h +++ b/lib/rbcodec/codecs/libopus/celt/_kiss_fft_guts.h @@ -90,11 +90,11 @@ do {(res).r = ADD32_ovflw((res).r,(a).r); (res).i = SUB32_ovflw((res).i,(a).i); \ }while(0) -#if defined(OPUS_ARM_INLINE_ASM) +#if defined(OPUS_ARM_ASM_ARMV4_ONLY) #include "arm/kiss_fft_armv4.h" #endif -#if defined(OPUS_ARM_INLINE_EDSP) +#if defined(OPUS_ARM_ASM_ARMV5E_AND_LATER) #include "arm/kiss_fft_armv5e.h" #endif #if defined(OPUS_CF_INLINE_ASM) @@ -160,14 +160,20 @@ # define KISS_FFT_COS(phase) floor(.5+TWID_MAX*cos (phase)) # define KISS_FFT_SIN(phase) floor(.5+TWID_MAX*sin (phase)) # define HALF_OF(x) ((x)>>1) +/* Unlike HALF_OF, this rounds rather than truncating. It stands in for a + multiply in kf_bfly5, where a floor bias costs measurable accuracy and the + extra add is far cheaper than the multiply it replaces. */ +# define QUARTER_OF(x) (((x)+2)>>2) #elif defined(USE_SIMD) # define KISS_FFT_COS(phase) _mm_set1_ps( cos(phase) ) # define KISS_FFT_SIN(phase) _mm_set1_ps( sin(phase) ) # define HALF_OF(x) ((x)*_mm_set1_ps(.5f)) +# define QUARTER_OF(x) ((x)*_mm_set1_ps(.25f)) #else # define KISS_FFT_COS(phase) (kiss_fft_scalar) cos(phase) # define KISS_FFT_SIN(phase) (kiss_fft_scalar) sin(phase) # define HALF_OF(x) ((x)*.5f) +# define QUARTER_OF(x) ((x)*.25f) #endif #define kf_cexp(x,phase) \ diff --git a/lib/rbcodec/codecs/libopus/celt/arch.h b/lib/rbcodec/codecs/libopus/celt/arch.h index 6437f59624..5b35810016 100644 --- a/lib/rbcodec/codecs/libopus/celt/arch.h +++ b/lib/rbcodec/codecs/libopus/celt/arch.h @@ -37,6 +37,19 @@ #include "opus_types.h" #include "opus_defines.h" +/* Placement of the hot decode path in fast on-chip memory. Rockbox config.h + sets this to ICODE_ATTR on the targets whose codec IRAM window has room; + everywhere else, and in the standalone test harness, it is nothing. */ +#ifndef ICODE_ATTR_OPUS +# define ICODE_ATTR_OPUS +#endif +#ifndef ICODE_ATTR_OPUS_MR +# define ICODE_ATTR_OPUS_MR +#endif +#ifndef ICONST_ATTR_OPUS_MR +# define ICONST_ATTR_OPUS_MR ICONST_ATTR +#endif + # if !defined(__GNUC_PREREQ) # if defined(__GNUC__)&&defined(__GNUC_MINOR__) # define __GNUC_PREREQ(_maj,_min) \ @@ -160,9 +173,9 @@ static OPUS_INLINE opus_int16 SAT16(opus_int32 x) { #ifdef OPUS_ARM_PRESUME_AARCH64_NEON_INTR #include "arm/fixed_arm64.h" -#elif defined (OPUS_ARM_INLINE_EDSP) +#elif defined (OPUS_ARM_ASM_ARMV5E_AND_LATER) #include "arm/fixed_armv5e.h" -#elif defined (OPUS_ARM_INLINE_ASM) +#elif defined (OPUS_ARM_ASM_ARMV4_ONLY) #include "arm/fixed_armv4.h" #elif defined (BFIN_ASM) #include "fixed_bfin.h" diff --git a/lib/rbcodec/codecs/libopus/celt/arm/bands_arm.h b/lib/rbcodec/codecs/libopus/celt/arm/bands_arm.h new file mode 100644 index 0000000000..3871b9785e --- /dev/null +++ b/lib/rbcodec/codecs/libopus/celt/arm/bands_arm.h @@ -0,0 +1,63 @@ +/* Copyright (c) 2025 Xiph.Org Foundation and contributors + Copyright (c) 2026 Michael Giacomelli + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef BANDS_ARM_H +#define BANDS_ARM_H + +/* Hand-written inner loops for celt/bands.c on both ARM generations. See + celt/arm/denorm_armv4_asm.S and denorm_armv5e_asm.S. Both are bit-exact + with the C they replace. + + Build with OPUS_ARM_NO_BANDS_ASM to select the C loops instead, which is + how the two are compared. */ + +#if defined(FIXED_POINT) && !defined(OPUS_ARM_NO_BANDS_ASM) + +# if defined(OPUS_ARM_ASM_ARMV4_ONLY) && (ARM_ARCH == 4) + +# define OVERRIDE_DENORM_BAND +void denorm_band_armv4(celt_sig *f, const celt_norm *x, int n, int g, + int shift); +# define DENORM_BAND denorm_band_armv4 + +# elif defined(OPUS_ARM_ASM_ARMV5E_AND_LATER) && (ARM_ARCH >= 5) + +# define OVERRIDE_DENORM_BAND +void denorm_band_armv5e(celt_sig *f, const celt_norm *x, int n, int g, + int shift); +# define DENORM_BAND denorm_band_armv5e + +/* ARMv5E only: on ARMv4 haar1 is multiply-bound and gains only the hoisted + rounding add. bands.h has already declared haar1 by this point, so the + macro renames the calls and the guarded C definition drops out. */ +# define OVERRIDE_haar1 +void haar1_armv5e(celt_norm *X, int N0, int stride); +# define haar1(X, N0, stride) haar1_armv5e((X), (N0), (stride)) + +# endif + +#endif + +#endif /* BANDS_ARM_H */ diff --git a/lib/rbcodec/codecs/libopus/celt/arm/comb_filter_arm.h b/lib/rbcodec/codecs/libopus/celt/arm/comb_filter_arm.h new file mode 100644 index 0000000000..04dfdea99a --- /dev/null +++ b/lib/rbcodec/codecs/libopus/celt/arm/comb_filter_arm.h @@ -0,0 +1,87 @@ +/* Copyright (c) 2025 Xiph.Org Foundation and contributors + Copyright (c) 2026 Michael Giacomelli + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef COMB_FILTER_ARM_H +#define COMB_FILTER_ARM_H + +/* Hand-written comb_filter_const for both ARM generations, reached through + the OVERRIDE_COMB_FILTER_CONST hook celt.h already provides. Both are + bit-exact with the C they replace; see the assembly for why the reordered + accumulation is exact rather than merely close. + + Neither implements the CUSTOM_MODES scalar tail, so the override stands + aside when that is defined. Build with OPUS_ARM_NO_COMB_ASM to select the + C version instead, which is how the two are compared. */ + +#if defined(FIXED_POINT) && !defined(CUSTOM_MODES) \ + && !defined(OPUS_ARM_NO_COMB_ASM) + +# if defined(OPUS_ARM_ASM_ARMV4_ONLY) && (ARM_ARCH == 4) + +# define OVERRIDE_COMB_FILTER_CONST +void comb_filter_const_armv4(opus_val32 *y, opus_val32 *x, int T, int N, + opus_val16 g10, opus_val16 g11, opus_val16 g12); +# define comb_filter_const(y, x, T, N, g10, g11, g12, arch) \ + ((void)(arch), comb_filter_const_armv4((y), (x), (T), (N), \ + (g10), (g11), (g12))) + +/* The SIG_SAT guard celt_synthesis applies to the IMDCT output before the + postfilter reads it; lives with the comb filter it protects. Replaces the + static inline celt_sat in celt_decoder.c. */ +# define OVERRIDE_CELT_SAT +void celt_sat_armv4(celt_sig *x, int n); +# define celt_sat(x, n) celt_sat_armv4((x), (n)) + +/* deemphasis_stereo_simple, the last stage before PCM. */ +# define OVERRIDE_DEEMPH_STEREO +void deemph_stereo_armv4(celt_sig *x0, celt_sig *x1, opus_val16 *pcm, int N, + int coef0, celt_sig *mem); +# define deemphasis_stereo_simple(in, pcm, N, coef0, mem) \ + deemph_stereo_armv4((in)[0], (in)[1], (pcm), (N), (coef0), (mem)) + +# elif defined(OPUS_ARM_ASM_ARMV5E_AND_LATER) && (ARM_ARCH >= 5) + +# define OVERRIDE_COMB_FILTER_CONST +void comb_filter_const_armv5e(opus_val32 *y, opus_val32 *x, int T, int N, + opus_val16 g10, opus_val16 g11, opus_val16 g12); +# define comb_filter_const(y, x, T, N, g10, g11, g12, arch) \ + ((void)(arch), comb_filter_const_armv5e((y), (x), (T), (N), \ + (g10), (g11), (g12))) + +# define OVERRIDE_CELT_SAT +void celt_sat_armv5e(celt_sig *x, int n); +# define celt_sat(x, n) celt_sat_armv5e((x), (n)) + +# define OVERRIDE_DEEMPH_STEREO +void deemph_stereo_armv5e(celt_sig *x0, celt_sig *x1, opus_val16 *pcm, int N, + int coef0, celt_sig *mem); +# define deemphasis_stereo_simple(in, pcm, N, coef0, mem) \ + deemph_stereo_armv5e((in)[0], (in)[1], (pcm), (N), (coef0), (mem)) + +# endif + +#endif + +#endif /* COMB_FILTER_ARM_H */ diff --git a/lib/rbcodec/codecs/libopus/celt/arm/comb_filter_armv4_asm.S b/lib/rbcodec/codecs/libopus/celt/arm/comb_filter_armv4_asm.S new file mode 100644 index 0000000000..53e7b74d82 --- /dev/null +++ b/lib/rbcodec/codecs/libopus/celt/arm/comb_filter_armv4_asm.S @@ -0,0 +1,212 @@ +/* ARMv4 inner loop for the CELT comb filter (pitch postfilter). + * + * Copyright (c) 2007-2008 CSIRO + * Copyright (c) 2007-2008 Xiph.Org Foundation + * Copyright (c) 2008 Gregory Maxwell + * Copyright (c) 2026 Michael Giacomelli + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the conditions stated in + * celt/celt.c are met. + * + * Why this exists + * --------------- + * comb_filter_const_c is a 5-tap symmetric FIR on a delayed copy of the + * signal, unrolled five ways so the delay window rotates through registers + * instead of shifting. That needs five window values, three gains, three + * pointers and a saturation bound live at once, which is twelve of the + * fourteen registers before there is anywhere to accumulate. gcc gives up + * and spills the gains, so the compiled loop reloads all three of them and + * the input pointer from the stack for every single output sample: + * + * ldr r1, [sp, #4] @ the x pointer + * ldr r8, [r1] + * ldr r10, [sp, #8] @ g10 + * smull r1, r9, r0, r10 + * ... + * ldr fp, [sp, #24] @ g11 + * ... + * ldr r4, [sp, #12] @ g12 + * + * Nineteen instructions per sample, four of them reloading invariants, and + * 59.5% of the function's executed memory traffic is stack rather than + * signal. Here everything stays resident and the only stack access is one + * reload of the loop limit per group of five. + * + * Two things make that fit. The multiply-accumulates are reordered to run + * outer pair, inner pair, centre tap; each product is truncated inside its + * own instruction exactly as the C does, and summing the same three + * truncated values in a different order gives the same 32-bit result, so + * this is bit-exact rather than merely close. And the window register that + * the next output is about to overwrite is dead as soon as the outer pair + * has been formed, so it serves as the scratch the multiplies need. + * + * Note the loop bound. Without CUSTOM_MODES the C loop is + * "for (i=0;i> 16) + * + * where the window values are already doubled. \new is the slot the fresh + * delayed sample lands in, \dying the slot the next sample will overwrite, + * which is also the second term of the outer pair and, once that pair has + * been consumed, the scratch register for the multiplies. + * + * MULT16_32_Q16_armv4 puts the 16-bit operand in the top half of Rs and + * keeps the high word of the product, so the gains are pre-shifted. Using + * the gain as Rm and the data as Rs lets Rs double as RdLo, which ARMv4 + * permits and which is what frees the third register. + */ + .macro CF_ONE new, dying, center, p1a, p1b + ldr lr, [r2], #4 @ the fresh delayed sample, undoubled + ldr ip, [r1], #4 @ t = x[i] + add \dying, \dying, lr, lsl #1 @ outer pair, into the dying slot + mov \new, lr, lsl #1 @ and the window slot keeps it doubled + smull \dying, lr, r11, \dying @ lr = (g12 * outer) >> 16 + add ip, ip, lr + add \dying, \p1a, \p1b @ inner pair + smull \dying, lr, r10, \dying @ lr = (g11 * inner) >> 16 + add ip, ip, lr + smull \dying, lr, r9, \center @ lr = (g10 * centre) >> 16 + add ip, ip, lr + cmp ip, r3 @ SATURATE(t, SIG_SAT), branchless and + movgt ip, r3 @ with only the positive bound resident + cmn ip, r3 + rsblt ip, r3, #0 + str ip, [r0], #4 + .endm + +/* ------------------------------------------------------------------------ + * void comb_filter_const_armv4(opus_val32 *y, opus_val32 *x, int T, int N, + * opus_val16 g10, opus_val16 g11, opus_val16 g12) + * + * r0 y, r1 x, r2 the delayed read pointer, r3 SIG_SAT, r4-r8 the window, + * r9-r11 the pre-shifted gains, ip the accumulator, lr scratch. The loop + * limit is the one thing that does not fit, and it is read once per group. + * ------------------------------------------------------------------------ */ + .global comb_filter_const_armv4 + .type comb_filter_const_armv4, %function +comb_filter_const_armv4: + push {r4-r11, lr} + sub sp, sp, #4 + ldrsh r9, [sp, #40] @ g10 + ldrsh r10, [sp, #44] @ g11 + ldrsh r11, [sp, #48] @ g12 + mov r9, r9, lsl #16 + mov r10, r10, lsl #16 + mov r11, r11, lsl #16 + cmp r3, #5 @ a short or empty span does nothing, + blt .Lcf_done @ and keeps N out of the unsigned divide + /* groups = N/5, by the usual reciprocal, so the loop needs no counter */ + ldr lr, .Lcf_recip + umull ip, r4, r3, lr @ RdHi, RdLo and Rm must all differ + movs r4, r4, lsr #2 + beq .Lcf_done + add r4, r4, r4, lsl #2 @ 5 * groups + add r4, r1, r4, lsl #2 @ x once the last group has been read + str r4, [sp] + /* Prime the window from x[-T-2 .. -T+1], doubled, and leave the read + pointer at x[-T+2], which is the first fresh sample. */ + sub r2, r1, r2, lsl #2 @ &x[-T] + sub r2, r2, #8 @ &x[-T-2] + ldm r2!, {r5, r6, r7, r8} @ X4, X3, X2, X1 in address order + mov r5, r5, lsl #1 + mov r6, r6, lsl #1 + mov r7, r7, lsl #1 + mov r8, r8, lsl #1 + ldr r3, .Lcf_sat +.Lcf_loop: + CF_ONE r4, r5, r7, r8, r6 @ X0 = new, X4 dies, centre X2 + CF_ONE r5, r6, r8, r4, r7 @ X4 = new, X3 dies, centre X1 + CF_ONE r6, r7, r4, r5, r8 @ X3 = new, X2 dies, centre X0 + CF_ONE r7, r8, r5, r6, r4 @ X2 = new, X1 dies, centre X4 + CF_ONE r8, r4, r6, r7, r5 @ X1 = new, X0 dies, centre X3 + ldr lr, [sp] + cmp r1, lr + bne .Lcf_loop +.Lcf_done: + add sp, sp, #4 + pop {r4-r11, pc} + .size comb_filter_const_armv4, .-comb_filter_const_armv4 + + .align 2 +.Lcf_sat: + .word 300000000 @ SIG_SAT +.Lcf_recip: + .word 0xCCCCCCCD @ N/5 = (N * this) >> 34 + +/* ------------------------------------------------------------------------ + * void celt_sat_armv4(celt_sig *x, int n) + * + * x[i] = SATURATE(x[i], SIG_SAT) in place, the guard celt_synthesis puts on + * the IMDCT output before the postfilter reads it. gcc compiles it one + * sample at a time: ldr, two compare-and-move clamps against register-held + * bounds, a compare, str and a taken branch, about 13 cycles a sample. Four + * at a time, one ldm and one stm replace four ldr and four str, and the + * clamp needs only the positive bound. About 8 cycles a sample. + * ------------------------------------------------------------------------ */ + .macro SAT_ONE reg + cmp \reg, r3 + movgt \reg, r3 + cmn \reg, r3 + rsblt \reg, r3, #0 + .endm + + .align 2 + .global celt_sat_armv4 + .type celt_sat_armv4, %function +celt_sat_armv4: + push {r4-r7, lr} + ldr r3, .Lcf_sat + cmp r1, #4 + blt .Lsat4_tail +.Lsat4_four: + ldmia r0, {r4, r5, r6, r7} + sub r1, r1, #4 @ no flags: the clamps below set them + SAT_ONE r4 + SAT_ONE r5 + SAT_ONE r6 + SAT_ONE r7 + stmia r0!, {r4, r5, r6, r7} + cmp r1, #4 + bge .Lsat4_four +.Lsat4_tail: + cmp r1, #0 + ble .Lsat4_done +.Lsat4_one: + ldr r4, [r0] + sub r1, r1, #1 + SAT_ONE r4 + str r4, [r0], #4 + cmp r1, #0 + bgt .Lsat4_one +.Lsat4_done: + pop {r4-r7, pc} + .size celt_sat_armv4, .-celt_sat_armv4 + + .section .note.GNU-stack,"",%progbits diff --git a/lib/rbcodec/codecs/libopus/celt/arm/comb_filter_armv5e_asm.S b/lib/rbcodec/codecs/libopus/celt/arm/comb_filter_armv5e_asm.S new file mode 100644 index 0000000000..89b9057af0 --- /dev/null +++ b/lib/rbcodec/codecs/libopus/celt/arm/comb_filter_armv5e_asm.S @@ -0,0 +1,178 @@ +/* ARMv5E inner loop for the CELT comb filter (pitch postfilter). + * + * Copyright (c) 2007-2008 CSIRO + * Copyright (c) 2007-2008 Xiph.Org Foundation + * Copyright (c) 2008 Gregory Maxwell + * Copyright (c) 2026 Michael Giacomelli + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the conditions stated in + * celt/celt.c are met. + * + * Why this exists + * --------------- + * The same register shortage as the ARMv4 kernel, described in + * comb_filter_armv4_asm.S, and the same fix. On this architecture 47.7% of + * the function's executed memory traffic is stack rather than signal. + * + * Two things are cheaper here. MAC16_32_Q16 is a single smlawb, so the + * multiplies need no scratch pair and no pre-shifted gain, and two of the + * three gains ride in one register as a halfword pair that smlawb and + * smlawt select between. That buys back enough room to keep the loop limit + * resident too, so this loop touches the stack not at all. + * + * What does not help here is bursting. The loop reads five consecutive + * words at each of two places and writes five at a third, which looks like + * three ideal multi-register transfers, but holding five results at once + * needs five registers that the window and the gains have already spent, + * and on this core an ldm of n costs the same n cycles as n separate loads + * anyway. So the memory traffic stays one word at a time on purpose. + * + * The multiply-accumulates run outer pair, inner pair, centre tap rather + * than the C's order. Each product is truncated inside its own + * instruction, exactly as in the C, and summing the same three truncated + * values in a different order gives the same 32-bit result, so this is + * bit-exact. + * + * Note the loop bound. Without CUSTOM_MODES the C loop is + * "for (i=0;i> 16) + * + * where the window values are already doubled. \new is the slot the fresh + * delayed sample lands in and \dying the slot the next sample overwrites, + * which is also the second term of the outer pair and, once that pair is + * consumed, the scratch the inner pair is built in. + */ + .macro CF_ONE new, dying, center, p1a, p1b + ldr lr, [r2], #4 @ the fresh delayed sample, undoubled + ldr ip, [r1], #4 @ t = x[i] + add \dying, \dying, lr, lsl #1 @ outer pair, into the dying slot + mov \new, lr, lsl #1 @ and the window slot keeps it doubled + smlawb ip, \dying, r10, ip @ t += (outer * g12) >> 16 + add \dying, \p1a, \p1b @ inner pair + smlawt ip, \dying, r9, ip @ t += (inner * g11) >> 16 + smlawb ip, \center, r9, ip @ t += (centre * g10) >> 16 + cmp ip, r3 @ SATURATE(t, SIG_SAT), branchless and + movgt ip, r3 @ with only the positive bound resident + cmn ip, r3 + rsblt ip, r3, #0 + str ip, [r0], #4 + .endm + +/* ------------------------------------------------------------------------ + * void comb_filter_const_armv5e(opus_val32 *y, opus_val32 *x, int T, int N, + * opus_val16 g10, opus_val16 g11, + * opus_val16 g12) + * + * r0 y, r1 x, r2 the delayed read pointer, r3 SIG_SAT, r4-r8 the window, + * r9 g10 packed under g11, r10 g12, r11 the loop limit, ip the accumulator, + * lr scratch. Fourteen registers, nothing spilled. + * ------------------------------------------------------------------------ */ + .global comb_filter_const_armv5e + .type comb_filter_const_armv5e, %function +comb_filter_const_armv5e: + push {r4-r11, lr} + ldrh r9, [sp, #36] @ g10, zero-extended: smlawb reads 15:0 + ldrsh lr, [sp, #40] @ g11 + ldrsh r10, [sp, #44] @ g12 + orr r9, r9, lr, lsl #16 @ g10 low, g11 high + cmp r3, #5 @ a short or empty span does nothing, + blt .Lcf5_done @ and keeps N out of the unsigned divide + /* groups = N/5, by the usual reciprocal, so the loop needs no counter */ + ldr lr, .Lcf5_recip + umull ip, r11, r3, lr @ RdHi, RdLo and Rm must all differ + movs r11, r11, lsr #2 + beq .Lcf5_done + add r11, r11, r11, lsl #2 @ 5 * groups + add r11, r1, r11, lsl #2 @ x once the last group has been read + /* Prime the window from x[-T-2 .. -T+1], doubled, and leave the read + pointer at x[-T+2], which is the first fresh sample. */ + sub r2, r1, r2, lsl #2 @ &x[-T] + sub r2, r2, #8 @ &x[-T-2] + ldm r2!, {r5, r6, r7, r8} @ X4, X3, X2, X1 in address order + mov r5, r5, lsl #1 + mov r6, r6, lsl #1 + mov r7, r7, lsl #1 + mov r8, r8, lsl #1 + ldr r3, .Lcf5_sat +.Lcf5_loop: + CF_ONE r4, r5, r7, r8, r6 @ X0 = new, X4 dies, centre X2 + CF_ONE r5, r6, r8, r4, r7 @ X4 = new, X3 dies, centre X1 + CF_ONE r6, r7, r4, r5, r8 @ X3 = new, X2 dies, centre X0 + CF_ONE r7, r8, r5, r6, r4 @ X2 = new, X1 dies, centre X4 + CF_ONE r8, r4, r6, r7, r5 @ X1 = new, X0 dies, centre X3 + cmp r1, r11 + bne .Lcf5_loop +.Lcf5_done: + pop {r4-r11, pc} + .size comb_filter_const_armv5e, .-comb_filter_const_armv5e + + .align 2 +.Lcf5_sat: + .word 300000000 @ SIG_SAT +.Lcf5_recip: + .word 0xCCCCCCCD @ N/5 = (N * this) >> 34 + +/* ------------------------------------------------------------------------ + * void celt_sat_armv5e(celt_sig *x, int n) + * + * x[i] = SATURATE(x[i], SIG_SAT) in place, the guard celt_synthesis puts on + * the IMDCT output before the postfilter reads it. gcc compiles it one + * sample at a time, and the ldr feeds the first compare directly, which + * stalls. Four at a time here: an ldm, then a sub that sets no flags and so + * keeps the loaded values clear of their first use, four single-bound + * clamps and an stm. About 7.25 cycles a sample against 11. + * ------------------------------------------------------------------------ */ + .macro SAT_ONE reg + cmp \reg, r3 + movgt \reg, r3 + cmn \reg, r3 + rsblt \reg, r3, #0 + .endm + + .align 2 + .global celt_sat_armv5e + .type celt_sat_armv5e, %function +celt_sat_armv5e: + push {r4-r7, lr} + ldr r3, .Lcf5_sat + cmp r1, #4 + blt .Lsat5_tail +.Lsat5_four: + ldmia r0, {r4, r5, r6, r7} + sub r1, r1, #4 @ the gap, and no flags to disturb + SAT_ONE r4 + SAT_ONE r5 + SAT_ONE r6 + SAT_ONE r7 + stmia r0!, {r4, r5, r6, r7} + cmp r1, #4 + bge .Lsat5_four +.Lsat5_tail: + cmp r1, #0 + ble .Lsat5_done +.Lsat5_one: + ldr r4, [r0] + sub r1, r1, #1 + SAT_ONE r4 + str r4, [r0], #4 + cmp r1, #0 + bgt .Lsat5_one +.Lsat5_done: + pop {r4-r7, pc} + .size celt_sat_armv5e, .-celt_sat_armv5e + + .section .note.GNU-stack,"",%progbits diff --git a/lib/rbcodec/codecs/libopus/celt/arm/deemph_armv4_asm.S b/lib/rbcodec/codecs/libopus/celt/arm/deemph_armv4_asm.S new file mode 100644 index 0000000000..f81ba729e8 --- /dev/null +++ b/lib/rbcodec/codecs/libopus/celt/arm/deemph_armv4_asm.S @@ -0,0 +1,105 @@ +/* ARMv4 stereo de-emphasis for the CELT decoder output. + * + * Copyright (c) 2007-2008 CSIRO + * Copyright (c) 2007-2010 Xiph.Org Foundation + * Copyright (c) 2008 Gregory Maxwell + * Copyright (c) 2026 Michael Giacomelli + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the conditions stated in + * celt/celt_decoder.c are met. + * + * Why this exists + * --------------- + * deemphasis_stereo_simple is the decoder's last stage: a one-pole filter + * per channel and the conversion to 16-bit PCM. On the ARM7TDMI the two + * smull dominate and cannot be avoided, but the compiled loop also shifts + * both filter states left once per sample and clamps to 16 bits with four + * instructions and a register holding -32768. + * + * Here the state is kept as the high word of the smull and shifted inside + * the add that consumes it, "add r8, r8, r4, lsl #1", and the clamp is the + * three-instruction asr / teq / eorne form. The incoming state from mem[] + * is a full value, so the first sample adds it unshifted. See + * deemph_armv5e_asm.S for the clamp. + * + * Bit-exact: the high word of smull(tmp, coef0<<16) shifted left one is + * exactly MULT16_32_Q15_armv4, low bit dropped included. + */ + +#if defined(__thumb__) || defined(__thumb2__) +#error "deemph_armv4_asm.S must be assembled in ARM mode" +#endif + +/* These kernels are 2,892 bytes that carry 43% of the cycles an ARMv4 decode + * spends, which makes them the densest thing in the codec to put in IRAM. + * config.h sets OPUS_ARM_ICODE on the targets whose codec IRAM window has + * room; everywhere else this stays in .text. The firmware config.h it reads + * that from is assembly-safe, and libopus/config.h guards the rest of its + * includes with __ASSEMBLER__ so this include costs nothing here. */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef OPUS_ARM_ICODE + .section .icode, "ax", %progbits +#else + .text +#endif + .align 2 + + .macro PCM16 src, tmp + add \tmp, \src, #0x800 @ PSHR32(x, 12) + mov \tmp, \tmp, asr #12 + mov lr, \tmp, asr #15 + teq lr, \tmp, asr #31 + eorne \tmp, r7, \tmp, asr #31 + strh \tmp, [r2], #2 + .endm + +/* ------------------------------------------------------------------------ + * void deemph_stereo_armv4(celt_sig *x0, celt_sig *x1, opus_val16 *pcm, + * int N, int coef0, celt_sig *mem) + * + * r0 x0, r1 x1, r2 pcm, r6 the count, r4 and r5 the two filter states, + * r7 32767, r8 and r9 the two channel sums, r10 coef0<<16, r11 and lr + * scratch, ip mem. smull needs RdLo, RdHi and Rm distinct: lr, r4/r5, r8/r9. + * ------------------------------------------------------------------------ */ + .global deemph_stereo_armv4 + .type deemph_stereo_armv4, %function +deemph_stereo_armv4: + push {r4-r11, lr} + ldr r10, [sp, #36] @ coef0 + ldr ip, [sp, #40] @ mem + mov r10, r10, lsl #16 @ into the top half of Rs, as the C does + mov r6, r3 + ldmia ip, {r4, r5} @ m0, m1, full values + mov r7, #0x7F00 + orr r7, r7, #0xFF + cmp r6, #0 + ble .Lde4_store + ldr r8, [r0], #4 + ldr r9, [r1], #4 + add r8, r8, r4 @ first sample: the state as stored + add r9, r9, r5 + b .Lde4_body +.Lde4_loop: + ldr r8, [r0], #4 + ldr r9, [r1], #4 + add r8, r8, r4, lsl #1 @ tmp0 = x0[j] + m0 + add r9, r9, r5, lsl #1 @ tmp1 = x1[j] + m1 +.Lde4_body: + smull lr, r4, r8, r10 @ m0 = r4 << 1 + smull lr, r5, r9, r10 @ m1 = r5 << 1 + PCM16 r8, r11 @ pcm[2j] + PCM16 r9, r11 @ pcm[2j+1] + subs r6, r6, #1 + bne .Lde4_loop + mov r4, r4, lsl #1 + mov r5, r5, lsl #1 +.Lde4_store: + stmia ip, {r4, r5} @ mem[0], mem[1] + pop {r4-r11, pc} + .size deemph_stereo_armv4, .-deemph_stereo_armv4 + + .section .note.GNU-stack,"",%progbits diff --git a/lib/rbcodec/codecs/libopus/celt/arm/deemph_armv5e_asm.S b/lib/rbcodec/codecs/libopus/celt/arm/deemph_armv5e_asm.S new file mode 100644 index 0000000000..4cc26ac96a --- /dev/null +++ b/lib/rbcodec/codecs/libopus/celt/arm/deemph_armv5e_asm.S @@ -0,0 +1,97 @@ +/* ARMv5E stereo de-emphasis for the CELT decoder output. + * + * Copyright (c) 2007-2008 CSIRO + * Copyright (c) 2007-2010 Xiph.Org Foundation + * Copyright (c) 2008 Gregory Maxwell + * Copyright (c) 2026 Michael Giacomelli + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the conditions stated in + * celt/celt_decoder.c are met. + * + * Why this exists + * --------------- + * deemphasis_stereo_simple is the decoder's last stage: a one-pole filter + * per channel and the conversion to 16-bit PCM. The compiled loop feeds + * each channel's ldr straight into an add, a stall apiece, shifts both + * filter states left once per sample, and clamps to 16 bits with four + * instructions and a register holding -32768. + * + * Here the state is kept as the smulwb result and shifted inside the add + * that consumes it, "add r8, r8, r4, lsl #1", which removes two shifts per + * sample. The incoming state from mem[] is a full value, so the first + * sample adds it unshifted and joins the loop after the adds; nothing is + * assumed about its low bit. The clamp is the three-instruction form + * + * mov lr, y, asr #15 + * teq lr, y, asr #31 @ in range iff bits 31..15 agree + * eorne y, r7, y, asr #31 @ else 0x7FFF, or 0xFFFF8000 for negatives + * + * whose low halfword is exactly the clamped value strh stores. + * + * Bit-exact: smulwb followed by a shift is MULT16_32_Q15 on this + * architecture, the rounding shift is PSHR32(x, SIG_SHIFT), and additions + * wrap exactly as the C does. + */ + +#if defined(__thumb__) || defined(__thumb2__) +#error "deemph_armv5e_asm.S must be assembled in ARM mode" +#endif + + .text + .align 2 + + .macro PCM16 src, tmp + add \tmp, \src, #0x800 @ PSHR32(x, 12) + mov \tmp, \tmp, asr #12 + mov lr, \tmp, asr #15 + teq lr, \tmp, asr #31 + eorne \tmp, r7, \tmp, asr #31 + strh \tmp, [r2], #2 + .endm + +/* ------------------------------------------------------------------------ + * void deemph_stereo_armv5e(celt_sig *x0, celt_sig *x1, opus_val16 *pcm, + * int N, int coef0, celt_sig *mem) + * + * r0 x0, r1 x1, r2 pcm, r3 and r6 the count, r4 and r5 the two filter + * states, r7 32767, r8 and r9 the two channel sums, r10 coef0, r11 and lr + * scratch, ip mem. + * ------------------------------------------------------------------------ */ + .global deemph_stereo_armv5e + .type deemph_stereo_armv5e, %function +deemph_stereo_armv5e: + push {r4-r11, lr} + ldr r10, [sp, #36] @ coef0; smulwb reads its low halfword + ldr ip, [sp, #40] @ mem + mov r6, r3 + ldmia ip, {r4, r5} @ m0, m1, full values + mov r7, #0x7F00 + orr r7, r7, #0xFF + cmp r6, #0 + ble .Lde5_store + ldr r8, [r0], #4 + ldr r9, [r1], #4 + add r8, r8, r4 @ first sample: the state as stored + add r9, r9, r5 + b .Lde5_body +.Lde5_loop: + ldr r8, [r0], #4 + ldr r9, [r1], #4 + add r8, r8, r4, lsl #1 @ tmp0 = x0[j] + m0 + add r9, r9, r5, lsl #1 @ tmp1 = x1[j] + m1 +.Lde5_body: + smulwb r4, r8, r10 @ m0 = this << 1 + smulwb r5, r9, r10 @ m1 = this << 1 + PCM16 r8, r11 @ pcm[2j] + PCM16 r9, r11 @ pcm[2j+1] + subs r6, r6, #1 + bne .Lde5_loop + mov r4, r4, lsl #1 + mov r5, r5, lsl #1 +.Lde5_store: + stmia ip, {r4, r5} @ mem[0], mem[1] + pop {r4-r11, pc} + .size deemph_stereo_armv5e, .-deemph_stereo_armv5e + + .section .note.GNU-stack,"",%progbits diff --git a/lib/rbcodec/codecs/libopus/celt/arm/denorm_armv4_asm.S b/lib/rbcodec/codecs/libopus/celt/arm/denorm_armv4_asm.S new file mode 100644 index 0000000000..93c4e87374 --- /dev/null +++ b/lib/rbcodec/codecs/libopus/celt/arm/denorm_armv4_asm.S @@ -0,0 +1,86 @@ +/* ARMv4 inner loop for denormalise_bands. + * + * Copyright (c) 2007-2008 CSIRO + * Copyright (c) 2007-2009 Xiph.Org Foundation + * Copyright (c) 2008-2009 Gregory Maxwell + * Copyright (c) 2026 Michael Giacomelli + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the conditions stated in + * celt/bands.c are met. + * + * Why this exists + * --------------- + * The compiled loop costs about sixteen cycles per element on the ARM7TDMI: + * an ldrsh, a mul, a register-specified shift, a str, and a counter + * increment, compare and taken branch every element. + * + * On this core a packed word load does not pay, because unpacking two signed + * halfwords takes three shifts and costs what the second ldrsh saved. What + * does pay is the rest: the two results leave as one stm, three cycles + * rather than four, and the loop test and taken branch happen once per pair + * rather than once per element. About thirteen and a half cycles per + * element. + * + * Bit-exact: ldrsh gives the same sign-extended operand MULT16_16 casts to, + * the product fits in 32 bits, and the shift is the same arithmetic shift. + */ + +#if defined(__thumb__) || defined(__thumb2__) +#error "denorm_armv4_asm.S must be assembled in ARM mode" +#endif + +/* These kernels are 2,892 bytes that carry 43% of the cycles an ARMv4 decode + * spends, which makes them the densest thing in the codec to put in IRAM. + * config.h sets OPUS_ARM_ICODE on the targets whose codec IRAM window has + * room; everywhere else this stays in .text. The firmware config.h it reads + * that from is assembly-safe, and libopus/config.h guards the rest of its + * includes with __ASSEMBLER__ so this include costs nothing here. */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef OPUS_ARM_ICODE + .section .icode, "ax", %progbits +#else + .text +#endif + .align 2 + +/* ------------------------------------------------------------------------ + * void denorm_band_armv4(celt_sig *f, const celt_norm *x, int n, + * int g, int shift) + * + * f[i] = (x[i]*g) >> shift for i in [0,n), with 0 <= shift <= 31. n is at + * least 1 in every call from denormalise_bands; a non-positive n writes + * nothing. mul requires Rd and Rm to differ, so g is Rm throughout. + * ------------------------------------------------------------------------ */ + .global denorm_band_armv4 + .type denorm_band_armv4, %function +denorm_band_armv4: + push {r4, lr} + ldr ip, [sp, #8] @ shift + subs r2, r2, #2 @ r2 = elements left after the next pair + blt .Ldb4_tail +.Ldb4_pair: + ldrsh r4, [r1], #2 + ldrsh lr, [r1], #2 + mul r4, r3, r4 @ x[i] * g + mul lr, r3, lr @ x[i+1] * g + mov r4, r4, asr ip + mov lr, lr, asr ip + stmia r0!, {r4, lr} @ f[i] = r4, f[i+1] = lr + subs r2, r2, #2 + bge .Ldb4_pair +.Ldb4_tail: + adds r2, r2, #2 @ 0 or 1 left, or negative for n <= 0 + ble .Ldb4_done + ldrsh r4, [r1], #2 + mul lr, r3, r4 + mov lr, lr, asr ip + str lr, [r0], #4 +.Ldb4_done: + pop {r4, pc} + .size denorm_band_armv4, .-denorm_band_armv4 + + .section .note.GNU-stack,"",%progbits diff --git a/lib/rbcodec/codecs/libopus/celt/arm/denorm_armv5e_asm.S b/lib/rbcodec/codecs/libopus/celt/arm/denorm_armv5e_asm.S new file mode 100644 index 0000000000..9bda4cc162 --- /dev/null +++ b/lib/rbcodec/codecs/libopus/celt/arm/denorm_armv5e_asm.S @@ -0,0 +1,90 @@ +/* ARMv5E inner loop for denormalise_bands. + * + * Copyright (c) 2007-2008 CSIRO + * Copyright (c) 2007-2009 Xiph.Org Foundation + * Copyright (c) 2008-2009 Gregory Maxwell + * Copyright (c) 2026 Michael Giacomelli + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the conditions stated in + * celt/bands.c are met. + * + * Why this exists + * --------------- + * The compiled loop is seven instructions that cost twelve cycles per + * element on the ARM926: + * + * ldrh r3, [r4], #2 + * smulbb r3, r3, r1 @ uses r3 at once: a two-cycle stall + * add r0, r0, #1 + * asr r3, r3, r7 @ register-specified shift, two cycles + * cmp ip, r0 + * str r3, [lr], #4 + * bgt .L324 @ three + * + * celt_norm is opus_int16, so two coefficients are one word. One ldr + * fetches both, smulbb and smultb take the halves apart, and the pair of + * results leaves as one stm. A subs between the load and its first use + * keeps the load clear of the stall and doubles as the loop test. That is + * about six and a half cycles per element. + * + * Bit-exact: smulbb and smultb multiply exactly the signed 16-bit operands + * MULT16_16 casts to, and the shift is the same arithmetic shift. + * + * The word load needs x on a word boundary. celt_norm arrays are only + * halfword-aligned, so one scalar element runs first when x is not. + */ + +#if defined(__thumb__) || defined(__thumb2__) +#error "denorm_armv5e_asm.S must be assembled in ARM mode" +#endif + + .text + .align 2 + +/* ------------------------------------------------------------------------ + * void denorm_band_armv5e(celt_sig *f, const celt_norm *x, int n, + * int g, int shift) + * + * f[i] = (x[i]*g) >> shift for i in [0,n), with 0 <= shift <= 31. n is at + * least 1 in every call from denormalise_bands, since band edges strictly + * increase; a non-positive n writes nothing. + * ------------------------------------------------------------------------ */ + .global denorm_band_armv5e + .type denorm_band_armv5e, %function +denorm_band_armv5e: + push {r4, r5, lr} + ldr ip, [sp, #12] @ shift + cmp r2, #0 + ble .Ldb5_done + tst r1, #2 @ one element to reach a word boundary? + beq .Ldb5_aligned + ldrsh r4, [r1], #2 + sub r2, r2, #1 + smulbb r5, r4, r3 + mov r5, r5, asr ip + str r5, [r0], #4 +.Ldb5_aligned: + subs r2, r2, #2 @ r2 = elements left after the next pair + blt .Ldb5_tail +.Ldb5_pair: + ldr r4, [r1], #4 @ x[i] low halfword, x[i+1] high + subs r2, r2, #2 @ loop test, and the gap before r4 is used + smulbb r5, r4, r3 @ x[i] * g + smultb lr, r4, r3 @ x[i+1] * g + mov r5, r5, asr ip + mov lr, lr, asr ip + stmia r0!, {r5, lr} @ f[i] = r5, f[i+1] = lr + bge .Ldb5_pair +.Ldb5_tail: + adds r2, r2, #2 @ 0 or 1 left + beq .Ldb5_done + ldrsh r4, [r1], #2 + smulbb r5, r4, r3 + mov r5, r5, asr ip + str r5, [r0], #4 +.Ldb5_done: + pop {r4, r5, pc} + .size denorm_band_armv5e, .-denorm_band_armv5e + + .section .note.GNU-stack,"",%progbits diff --git a/lib/rbcodec/codecs/libopus/celt/arm/exp_rotation1_armv4_asm.S b/lib/rbcodec/codecs/libopus/celt/arm/exp_rotation1_armv4_asm.S new file mode 100644 index 0000000000..2666f7a2b2 --- /dev/null +++ b/lib/rbcodec/codecs/libopus/celt/arm/exp_rotation1_armv4_asm.S @@ -0,0 +1,200 @@ +/* Copyright (c) 2026 Michael Giacomelli + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the conditions stated in + * celt/kiss_fft.c are met. + */ + +/* ARMv4 exp_rotation1, the largest single function in an ARM7TDMI decode. + * + * The chain of Givens rotations it applies writes X[i+stride] and then reads + * it back as the next iteration's x1, so at stride 1 -- 58% of the executed + * iterations -- one of the two loads is redundant and one of the two stores + * is dead: nothing reads that slot before the next iteration overwrites it, + * so its store waits until the chain ends. Both loops below carry that + * value in a register, forwards as the next x1 and backwards as the next x2. + * + * What ARMv4 has to do that ARMv5E does not is truncate it. The ARMv5E + * kernel carries the full 32-bit shifted value because every later use is + * the bottom halfword operand of smulbb, which reads exactly the bits that + * would have been stored. Here the use is mul, which reads all 32, so the + * carried value has to be the halfword EXTRACT16 would have written: + * + * mov rC, rY, lsl #1 @ bit 30 to bit 31 + * mov rC, rC, asr #16 @ bits 30..15, sign extended + * + * which is PSHR32(y,15) narrowed to 16 bits, in the two instructions that + * replace the one shift -- so the carry costs one cycle and saves four. + * + * The multiplies are the floor and are left alone. Every operand is a + * sign-extended halfword, so the multiplier finishes in two passes: the four + * of them are 14 cycles of the loop's 27, measured, and no rearrangement + * (including the three-multiply complex product) improves on that once the + * multiplier is credited with stopping early. + * + * A larger stride gets the plain loop. Its elements split into stride + * independent chains that could each be carried the same way, but the chains + * are about three steps long at the sizes CELT uses, and priming a carry and + * flushing its deferred store costs more than the three steps save. + * + * Bit-exact against the C, forwards and backwards, at every stride. + */ + +#if defined(__thumb__) || defined(__thumb2__) +#error "exp_rotation1_armv4_asm.S must be assembled in ARM mode" +#endif + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#if defined(OPUS_ARM_ICODE) && !defined(OPUS_NO_ICODE) + .section .icode,"ax",%progbits +#else + .text +#endif + .align 2 + +/* One carried step of the forward chain: x1 is in r7 and stays there as the + next iteration's x1, and the store of X[i+1] waits until the chain ends + because the next iteration reads it out of the register. */ + .macro ER4_FWD + ldrsh r8, [r10, #2] @ x2 = X[i+1] + mul ip, r4, r7 @ s*x1, and the gap before x2 is used + mul r9, r5, r8 @ ms*x2 + mla ip, r3, r8, ip @ c*x2 + s*x1 + mla r9, r3, r7, r9 @ c*x1 - s*x2 + add ip, ip, #0x4000 + add r9, r9, #0x4000 + mov r7, ip, lsl #1 @ X[i+1], carried as the next x1, + mov r7, r7, asr #16 @ narrowed as EXTRACT16 would store it + mov r9, r9, asr #15 + strh r9, [r10], #2 @ X[i] + .endm + +/* The same backwards, where the carried value is X[i] and becomes the next + iteration's x2. */ + .macro ER4_BWD + ldrsh r7, [r10, #-2]! @ x1 = X[i], r10 = &X[i] + mul r9, r5, r8 @ ms*x2, and the gap before x1 is used + mul ip, r4, r7 @ s*x1 + mla r9, r3, r7, r9 @ c*x1 - s*x2 + mla ip, r3, r8, ip @ c*x2 + s*x1 + add r9, r9, #0x4000 + add ip, ip, #0x4000 + mov r8, r9, lsl #1 @ X[i], carried as the next x2 + mov r8, r8, asr #16 + mov ip, ip, asr #15 + strh ip, [r10, #2] @ X[i+1] + .endm + +/* ------------------------------------------------------------------------ + * void exp_rotation1_armv4(celt_norm *X, int len, int stride, + * opus_val16 c, opus_val16 s) + * + * Forward over i = 0 .. len-stride-1, then backward over + * i = len-2*stride-1 .. 0, each step + * x1 = X[i], x2 = X[i+stride] + * X[i+stride] = (c*x2 + s*x1 + 16384) >> 15 + * X[i] = (c*x1 - s*x2 + 16384) >> 15 + * + * r0 X, r1 len, r2 stride, r3 c, r4 s, r5 ms, r6 count, r7 x1, r8 x2, + * r9 and ip the two accumulators, r10 the walking pointer. + * ------------------------------------------------------------------------ */ + .global exp_rotation1_armv4 + .type exp_rotation1_armv4, %function +exp_rotation1_armv4: + push {r4-r10, lr} + ldrsh r4, [sp, #32] @ s + rsb r5, r4, #0 @ ms = NEG16(s), and narrowed to sixteen + mov r5, r5, lsl #16 @ bits so that it wraps where NEG16 does. + mov r5, r5, asr #16 @ mul reads all 32 bits, unlike smulbb, + @ so the ARMv5E kernel can skip this and + @ this one cannot. + cmp r2, #1 + bne .Ler4_general + + /* ---- stride 1, forward: carry X[i+1] into the next x1 ---- */ + subs r6, r1, #1 @ len-1 iterations + ble .Ler4_back1 + mov r10, r0 + ldrsh r7, [r10] @ x1 = X[0] + tst r6, #1 + beq .Ler4_f1 @ even already + ER4_FWD @ peel one, so the pairs loop divides + subs r6, r6, #1 + beq .Ler4_f1_end +.Ler4_f1: + ER4_FWD + ER4_FWD + subs r6, r6, #2 + bne .Ler4_f1 +.Ler4_f1_end: + strh r7, [r10] @ the last X[i+1] + + /* ---- stride 1, backward: carry X[i] into the next x2 ---- */ +.Ler4_back1: + subs r6, r1, #2 @ len-2 iterations + ble .Ler4_done + add r10, r0, r6, lsl #1 @ &X[len-2] + ldrsh r8, [r10] @ x2 = X[len-2] + tst r6, #1 + beq .Ler4_b1 + ER4_BWD + subs r6, r6, #1 + beq .Ler4_b1_end +.Ler4_b1: + ER4_BWD + ER4_BWD + subs r6, r6, #2 + bne .Ler4_b1 +.Ler4_b1_end: + strh r8, [r10] @ the last X[i], which is X[0] + b .Ler4_done + + /* ---- any other stride: both loads, both stores ---- */ +.Ler4_general: + mov r2, r2, lsl #1 @ stride in bytes, numerically 2*stride + subs r6, r1, r2, lsr #1 @ len-stride iterations + ble .Ler4_backg + mov r10, r0 +.Ler4_fg: + ldrsh r7, [r10] @ x1 + ldrsh r8, [r10, r2] @ x2 + mul ip, r4, r7 @ s*x1 + mul r9, r5, r8 @ ms*x2 + mla ip, r3, r8, ip @ c*x2 + s*x1 + mla r9, r3, r7, r9 @ c*x1 - s*x2 + add ip, ip, #0x4000 + add r9, r9, #0x4000 + mov ip, ip, asr #15 + mov r9, r9, asr #15 + strh ip, [r10, r2] @ X[i+stride] + subs r6, r6, #1 + strh r9, [r10], #2 @ X[i] + bne .Ler4_fg +.Ler4_backg: + subs r6, r1, r2 @ len-2*stride iterations + ble .Ler4_done + sub r10, r6, #1 + add r10, r0, r10, lsl #1 @ &X[len-2*stride-1] +.Ler4_bg: + ldrsh r7, [r10] + ldrsh r8, [r10, r2] + mul ip, r4, r7 + mul r9, r5, r8 + mla ip, r3, r8, ip + mla r9, r3, r7, r9 + add ip, ip, #0x4000 + add r9, r9, #0x4000 + mov ip, ip, asr #15 + mov r9, r9, asr #15 + strh ip, [r10, r2] + subs r6, r6, #1 + strh r9, [r10], #-2 + bne .Ler4_bg +.Ler4_done: + pop {r4-r10, pc} + .size exp_rotation1_armv4, .-exp_rotation1_armv4 + + .section .note.GNU-stack,"",%progbits diff --git a/lib/rbcodec/codecs/libopus/celt/arm/exp_rotation1_armv5e_asm.S b/lib/rbcodec/codecs/libopus/celt/arm/exp_rotation1_armv5e_asm.S new file mode 100644 index 0000000000..0a01472f29 --- /dev/null +++ b/lib/rbcodec/codecs/libopus/celt/arm/exp_rotation1_armv5e_asm.S @@ -0,0 +1,163 @@ +/* ARMv5E exp_rotation1 for the CELT spreading rotation. + * + * Copyright (c) 2007-2008 CSIRO + * Copyright (c) 2007-2009 Xiph.Org Foundation + * Copyright (c) 2007-2009 Timothy B. Terriberry + * Copyright (c) 2026 Michael Giacomelli + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the conditions stated in + * celt/vq.c are met. + * + * Why this exists + * --------------- + * The compiled loop loads x1 and x2 with ldrsh and hands each straight to + * smulbb, two two-cycle stalls per iteration, 18% of the function on the + * ARM926. Every load here sits at least two instructions ahead of its first + * use. + * + * At stride 1, which is 82 of 148 calls, the loop also reloads the value it + * just wrote: Xptr[stride] becomes the next Xptr[0]. Carrying it instead + * saves one load per iteration, and since nothing reads that slot again + * before the next iteration overwrites it, its store is deferred to the end + * of the loop, saving one store too. + * + * Bit-exact. The carried register holds the full 32-bit shifted value + * rather than the 16-bit value EXTRACT16 would store, but every later use of + * it is the bottom-halfword operand of smulbb or smlabb, which reads exactly + * the bits that would have been stored, and the final strh stores them. + */ + +#if defined(__thumb__) || defined(__thumb2__) +#error "exp_rotation1_armv5e_asm.S must be assembled in ARM mode" +#endif + + .text + .align 2 + +/* ------------------------------------------------------------------------ + * void exp_rotation1_armv5e(celt_norm *X, int len, int stride, + * opus_val16 c, opus_val16 s) + * + * Forward over i = 0 .. len-stride-1, then backward over + * i = len-2*stride-1 .. 0, each step + * x1 = X[i], x2 = X[i+stride] + * X[i+stride] = (c*x2 + s*x1 + 16384) >> 15 + * X[i] = (c*x1 - s*x2 + 16384) >> 15 + * + * r0 X, r1 len, r2 stride, r3 c, r4 s, r5 -s, r6 count, r7 x1, r8 x2, + * r9 and ip the two results, r10 the walking pointer. + * ------------------------------------------------------------------------ */ + .global exp_rotation1_armv5e + .type exp_rotation1_armv5e, %function +exp_rotation1_armv5e: + push {r4-r10, lr} + ldr r4, [sp, #32] @ s + rsb r5, r4, #0 @ ms; its low halfword wraps as NEG16 does + cmp r2, #1 + bne .Ler_general + + /* ---- stride 1, forward: carry X[i+1] into the next x1 ---- */ + subs r6, r1, #1 @ len-1 iterations + ble .Ler_back1 + mov r10, r0 + ldrsh r7, [r10] @ x1 = X[0] +.Ler_f1: + ldrsh r8, [r10, #2] @ x2 = X[i+1] + smulbb r9, r3, r7 @ c*x1 + subs r6, r6, #1 @ the gap before r8 is used + smulbb ip, r3, r8 @ c*x2 + smlabb r9, r5, r8, r9 @ c*x1 - s*x2 + smlabb ip, r4, r7, ip @ c*x2 + s*x1 +#ifndef OPUS_ARM_FAST_ROUND + add r9, r9, #0x4000 +#endif +#ifndef OPUS_ARM_FAST_ROUND + add ip, ip, #0x4000 +#endif + mov r9, r9, asr #15 + mov r7, ip, asr #15 @ X[i+1], carried as the next x1 + strh r9, [r10], #2 @ X[i] + bne .Ler_f1 + strh r7, [r10] @ the last X[i+1] + + /* ---- stride 1, backward: carry X[i] into the next x2 ---- */ +.Ler_back1: + subs r6, r1, #2 @ len-2 iterations + ble .Ler_done + add r10, r0, r6, lsl #1 @ &X[len-2] + ldrsh r8, [r10] @ x2 = X[len-2] +.Ler_b1: + ldrsh r7, [r10, #-2]! @ x1 = X[i], r10 = &X[i] + smulbb ip, r3, r8 @ c*x2 + subs r6, r6, #1 + smulbb r9, r3, r7 @ c*x1 + smlabb ip, r4, r7, ip @ c*x2 + s*x1 + smlabb r9, r5, r8, r9 @ c*x1 - s*x2 +#ifndef OPUS_ARM_FAST_ROUND + add ip, ip, #0x4000 +#endif +#ifndef OPUS_ARM_FAST_ROUND + add r9, r9, #0x4000 +#endif + mov ip, ip, asr #15 + mov r8, r9, asr #15 @ X[i], carried as the next x2 + strh ip, [r10, #2] @ X[i+1] + bne .Ler_b1 + strh r8, [r10] @ the last X[i], which is X[0] + b .Ler_done + + /* ---- any other stride: both loads, both stores ---- */ +.Ler_general: + mov r2, r2, lsl #1 @ stride in bytes, numerically 2*stride + subs r6, r1, r2, lsr #1 @ len-stride iterations + ble .Ler_backg + mov r10, r0 +.Ler_fg: + ldrsh r7, [r10] @ x1 + ldrsh r8, [r10, r2] @ x2 + subs r6, r6, #1 + smulbb r9, r3, r7 + smulbb ip, r3, r8 + smlabb r9, r5, r8, r9 @ c*x1 - s*x2 + smlabb ip, r4, r7, ip @ c*x2 + s*x1 +#ifndef OPUS_ARM_FAST_ROUND + add r9, r9, #0x4000 +#endif +#ifndef OPUS_ARM_FAST_ROUND + add ip, ip, #0x4000 +#endif + mov r9, r9, asr #15 + mov ip, ip, asr #15 + strh ip, [r10, r2] @ X[i+stride] + strh r9, [r10], #2 @ X[i] + bne .Ler_fg +.Ler_backg: + subs r6, r1, r2 @ len-2*stride iterations + ble .Ler_done + sub r10, r6, #1 + add r10, r0, r10, lsl #1 @ &X[len-2*stride-1] +.Ler_bg: + ldrsh r7, [r10] + ldrsh r8, [r10, r2] + subs r6, r6, #1 + smulbb r9, r3, r7 + smulbb ip, r3, r8 + smlabb r9, r5, r8, r9 + smlabb ip, r4, r7, ip +#ifndef OPUS_ARM_FAST_ROUND + add r9, r9, #0x4000 +#endif +#ifndef OPUS_ARM_FAST_ROUND + add ip, ip, #0x4000 +#endif + mov r9, r9, asr #15 + mov ip, ip, asr #15 + strh ip, [r10, r2] + strh r9, [r10], #-2 + bne .Ler_bg +.Ler_done: + pop {r4-r10, pc} + .size exp_rotation1_armv5e, .-exp_rotation1_armv5e + + .section .note.GNU-stack,"",%progbits diff --git a/lib/rbcodec/codecs/libopus/celt/arm/haar1_armv5e_asm.S b/lib/rbcodec/codecs/libopus/celt/arm/haar1_armv5e_asm.S new file mode 100644 index 0000000000..68dc0248c0 --- /dev/null +++ b/lib/rbcodec/codecs/libopus/celt/arm/haar1_armv5e_asm.S @@ -0,0 +1,89 @@ +/* ARMv5E haar1 for the CELT band recombination. + * + * Copyright (c) 2007-2008 CSIRO + * Copyright (c) 2007-2009 Xiph.Org Foundation + * Copyright (c) 2008-2009 Gregory Maxwell + * Copyright (c) 2026 Michael Giacomelli + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the conditions stated in + * celt/bands.c are met. + * + * Why this exists + * --------------- + * The compiled pair loop hands each ldrh straight to smulbb, two two-cycle + * stalls per pair, and adds the rounding constant to both results. That + * stall accounts for all 15,216 of the interlock cycles the profile charges + * to haar1. Here both loads run ahead of their first use, and the rounding + * term joins the shared product once: + * + * (t1 + t2 + R) = (t1 + R) + t2, (t1 - t2 + R) = (t1 + R) - t2 + * + * Both loads stay. haar1 averages a stride of about 2, so the two elements + * are rarely adjacent and a packed word load would seldom apply. + * + * Bit-exact unless OPUS_ARM_FAST_ROUND is defined, which drops the rounding + * term and truncates instead. Measured against the rounded build, together + * with the same change in exp_rotation1: 742 of 15,360 decoded samples + * differ, at most 2 LSB, 83.1 dB. + */ + +#if defined(__thumb__) || defined(__thumb2__) +#error "haar1_armv5e_asm.S must be assembled in ARM mode" +#endif + + .text + .align 2 + +/* ------------------------------------------------------------------------ + * void haar1_armv5e(celt_norm *X, int N0, int stride) + * + * For each i < stride and j < N0/2, with a = X[2j*stride+i] and + * b = X[(2j+1)*stride+i]: + * X[2j*stride+i] = (23170*a + 23170*b + 16384) >> 15 + * X[(2j+1)*stride+i] = (23170*a - 23170*b + 16384) >> 15 + * + * r0 X, r1 pairs, r2 stride, r3 stride in bytes, r4 pair step in bytes, + * r5 i, r6 walking pointer, r7 count, r8 and lr the two elements, r9 scratch, + * ip the constant. + * ------------------------------------------------------------------------ */ + .global haar1_armv5e + .type haar1_armv5e, %function +haar1_armv5e: + push {r4-r9, lr} + movs r1, r1, asr #1 @ N0 >>= 1 + ble .Lh1_done + cmp r2, #0 + ble .Lh1_done + mov r3, r2, lsl #1 + mov r4, r2, lsl #2 + mov ip, #0x5A00 + orr ip, ip, #0x82 @ QCONST16(.70710678f,15) = 23170 + mov r5, #0 +.Lh1_outer: + add r6, r0, r5, lsl #1 @ &X[i] + mov r7, r1 +.Lh1_pair: + ldrsh r8, [r6] @ a + ldrsh lr, [r6, r3] @ b + subs r7, r7, #1 @ loop test, and the gap before use + smulbb r8, ip, r8 @ t1 = 23170*a + smulbb lr, ip, lr @ t2 = 23170*b +#ifndef OPUS_ARM_FAST_ROUND + add r8, r8, #0x4000 @ t1 + R, shared by both results +#endif + add r9, r8, lr + sub r8, r8, lr + mov r9, r9, asr #15 + mov r8, r8, asr #15 + strh r8, [r6, r3] @ X[(2j+1)*stride+i] + strh r9, [r6], r4 @ X[2j*stride+i], then step a pair + bne .Lh1_pair + add r5, r5, #1 + cmp r5, r2 + blt .Lh1_outer +.Lh1_done: + pop {r4-r9, pc} + .size haar1_armv5e, .-haar1_armv5e + + .section .note.GNU-stack,"",%progbits diff --git a/lib/rbcodec/codecs/libopus/celt/arm/kiss_fft_armv4.h b/lib/rbcodec/codecs/libopus/celt/arm/kiss_fft_armv4.h index e4faad6f2b..1a82380783 100644 --- a/lib/rbcodec/codecs/libopus/celt/arm/kiss_fft_armv4.h +++ b/lib/rbcodec/codecs/libopus/celt/arm/kiss_fft_armv4.h @@ -116,6 +116,31 @@ } \ while(0) +/* Hand-written radix bodies. See celt/arm/kiss_fft_armv4_asm.S for why: + briefly, the compiled kernels spill their loop-invariant pointers and + reload them every pass, and gcc will not form ldm/stm from contiguous C + accesses. The assembly keeps the bookkeeping resident and moves the data + in bursts. These take st->twiddles rather than st, so the assembly needs + no knowledge of the layout of kiss_fft_state. */ +#ifndef OPUS_ARM_NO_FFT_ASM +#define OVERRIDE_kf_bfly3 +#define OVERRIDE_kf_bfly4 +#define OVERRIDE_kf_bfly5 + +void kf_bfly3_armv4(kiss_fft_cpx *Fout, const kiss_twiddle_cpx *tw, + int fstride, int m, int N, int mm); +void kf_bfly4_armv4(kiss_fft_cpx *Fout, const kiss_twiddle_cpx *tw, + int fstride, int m, int N, int mm); +void kf_bfly5_armv4(kiss_fft_cpx *Fout, const kiss_twiddle_cpx *tw, + int fstride, int m, int N, int mm); + +#define kf_bfly3(Fout, fstride, st, m, N, mm) \ + kf_bfly3_armv4((Fout), (st)->twiddles, (int)(fstride), (m), (N), (mm)) +#define kf_bfly4(Fout, fstride, st, m, N, mm) \ + kf_bfly4_armv4((Fout), (st)->twiddles, (int)(fstride), (m), (N), (mm)) +#define kf_bfly5(Fout, fstride, st, m, N, mm) kf_bfly5_armv4((Fout), (st)->twiddles, (int)(fstride), (m), (N), (mm)) +#endif /* OPUS_ARM_NO_FFT_ASM */ + #endif /* FIXED_POINT */ #endif /* KISS_FFT_ARMv4_H */ diff --git a/lib/rbcodec/codecs/libopus/celt/arm/kiss_fft_armv4_asm.S b/lib/rbcodec/codecs/libopus/celt/arm/kiss_fft_armv4_asm.S new file mode 100644 index 0000000000..0f61657e52 --- /dev/null +++ b/lib/rbcodec/codecs/libopus/celt/arm/kiss_fft_armv4_asm.S @@ -0,0 +1,635 @@ +/* ARMv4 FFT butterflies for the CELT fixed-point kiss_fft. + * + * Copyright (c) 2003-2004, Mark Borgerding + * Copyright (c) 2005-2007, Xiph.Org Foundation + * Copyright (c) 2026 Michael Giacomelli + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the conditions stated in + * celt/kiss_fft.c are met. + * + * Why this exists + * --------------- + * On ARM7TDMI a single load costs 1S+1N+1I = 3 cycles and a store 2N = 2, + * while a load multiple of n registers costs n+2 and a store multiple n+1. + * Every extra word moved in a burst is two cycles cheaper to load and one + * cheaper to store, and on the PP502x, where instructions and data share one + * cache, a burst arbitrates against instruction fetch once instead of n + * times. + * + * gcc cannot be steered into that from C. It reorders the accesses while + * scheduling and does not hand out ascending register pairs, so contiguous + * loads come out as separate ldr. Worse, the radix kernels need more live + * values than ARM has registers, and gcc resolves that by spilling the + * loop-invariant pointers and strides and reloading them every pass: the + * compiled radix-4 body reloads eleven words from its frame per iteration. + * + * These routines invert that. Every pointer, twiddle pointer and counter + * stays in a register for the whole inner loop, and the transient butterfly + * values are what goes to the frame, moved in ldm/stm bursts. The spill is + * genuinely unavoidable -- a radix-4 pass needs four transformed points plus + * a complex multiply in flight, which is more than fourteen words -- but + * here it is placed rather than accidental. + * + * Complex multiply register discipline follows the C_MUL asm already in + * kiss_fft_armv4.h: the twiddle stays a 16-bit value in Rs, and the Q15 + * narrowing keeps all 32 bits of the product. MULT16_32_Q15_armv4, used by + * the C C_MULBYSCALAR, drops the low bit for speed, so the radix-3 twiddle + * by epi3 comes out one LSB more accurate here than in the C path. + */ + +#if defined(__thumb__) || defined(__thumb2__) +#error "kiss_fft_armv4_asm.S must be assembled in ARM mode" +#endif + +/* These kernels are 2,892 bytes that carry 43% of the cycles an ARMv4 decode + * spends, which makes them the densest thing in the codec to put in IRAM. + * config.h sets OPUS_ARM_ICODE on the targets whose codec IRAM window has + * room; everywhere else this stays in .text. The firmware config.h it reads + * that from is assembly-safe, and libopus/config.h guards the rest of its + * includes with __ASSEMBLER__ so this include costs nothing here. */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef OPUS_ARM_ICODE + .section .icode, "ax", %progbits +#else + .text +#endif + .align 2 + +/* ------------------------------------------------------------------------ + * void kf_bfly4_armv4(kiss_fft_cpx *Fout, const kiss_twiddle_cpx *tw, + * int fstride, int m, int N, int mm) + * + * r0 = Fout r1 = tw r2 = fstride r3 = m [sp+0] = N [sp+4] = mm + * ------------------------------------------------------------------------ */ + .global kf_bfly4_armv4 + .type kf_bfly4_armv4, %function +kf_bfly4_armv4: + push {r4-r11, lr} + cmp r3, #1 + bne .Lb4_general + +/* Degenerate case: every twiddle is 1, so four consecutive complex points -- + eight consecutive words -- go in and eight come out with no multiplies at + all. This is the one kernel that fits entirely in registers, so it is one + ldm of eight and one stm of eight per group. */ + ldr r1, [sp, #36] @ N +.Lb4_m1: + ldm r0, {r4, r5, r6, r7, r8, r9, r10, r11} + sub ip, r4, r8 @ s0.r = F0.r - F2.r + sub lr, r5, r9 @ s0.i + add r4, r4, r8 @ F0.r += F2.r + add r5, r5, r9 + add r8, r6, r10 @ s1.r = F1.r + F3.r + add r9, r7, r11 @ s1.i + sub r6, r6, r10 @ d.r = F1.r - F3.r + sub r7, r7, r11 @ d.i + sub r10, r4, r8 @ F2.r = F0.r - s1.r + sub r11, r5, r9 @ F2.i + add r4, r4, r8 @ F0.r += s1.r + add r5, r5, r9 + add r8, ip, r7 @ F1.r = s0.r + d.i + sub r9, lr, r6 @ F1.i = s0.i - d.r + sub ip, ip, r7 @ F3.r = s0.r - d.i + add lr, lr, r6 @ F3.i = s0.i + d.r + stmia r0!, {r4, r5, r8, r9, r10, r11, ip, lr} + subs r1, r1, #1 + bne .Lb4_m1 + pop {r4-r11, pc} + +/* Frame, 52 bytes. The first six words are the placed spill; everything + from 24 on is outer-loop state, read once per i and never inside the + inner loop. + 0 T1.r 4 T1.i 8 T0.r 12 T0.i 16 D.r 20 D.i + The outer-loop half is ordered to match r0-r6 at the end of the prologue, + so one stm stores it: 24 Fout, 28 tw, 32 fstride, 36 m, 40 N, 44 mm*8, + 48 m*8. */ +.Lb4_general: + sub sp, sp, #52 + ldr r4, [sp, #88] @ N + ldr r5, [sp, #92] @ mm + mov r2, r2, lsl #2 @ fstride in bytes (4 per kiss_twiddle_cpx) + mov r5, r5, lsl #3 @ mm * sizeof(kiss_fft_cpx) + mov r6, r3, lsl #3 @ M = m * sizeof(kiss_fft_cpx) + add ip, sp, #24 + stmia ip, {r0-r6} + +.Lb4_outer: + ldr r0, [sp, #24] @ Fout for this i + ldr r5, [sp, #44] @ mm*8 + add r5, r0, r5 + str r5, [sp, #24] @ Fout for i+1 + ldr r5, [sp, #48] @ M + add r1, r0, r5 @ p1 = Fout + m + add r2, r1, r5 @ p2 = Fout + 2m + add r3, r2, r5 @ p3 = Fout + 3m + ldr r4, [sp, #28] @ tw1 = twiddles + mov r5, r4 @ tw3 = twiddles + ldr r7, [sp, #36] @ inner count = m + +/* Pass 0 twiddles by twiddles[0], which is 1, so it is peeled: no twiddle + load and no multiply. In fixed point twiddles[0] is 32767 rather than + 32768, so skipping it also drops a small systematic gain error. */ + ldm r0, {r8, r9} @ A + ldm r2, {r6, r11} @ C + add r10, r8, r6 @ T0.r = A.r + C.r + sub r8, r8, r6 @ T1.r = A.r - C.r + add ip, r9, r11 @ T0.i + sub r9, r9, r11 @ T1.i + stmia sp, {r8, r9, r10, ip} + ldm r1, {r6, r11} @ B + ldm r3, {r8, r9} @ D + b .Lb4_combine + +.Lb4_inner: + ldr ip, [sp, #32] @ fstride, the one loop-invariant reload + add r4, r4, ip @ tw1 += fstride + add r5, r5, ip, lsl #1 + add r5, r5, ip @ tw3 += 3*fstride + /* tw2 sits halfway between the two. Deriving it keeps a register free; + written as a difference so the address arithmetic cannot overflow. */ + sub ip, r5, r4 + add ip, r4, ip, lsr #1 + + /* C = tw2 * Fout[2m] */ + ldm r2, {r8, r9} + ldrsh r10, [ip] + ldrsh ip, [ip, #2] + smull lr, r11, r9, r10 + smlal lr, r11, r8, ip + mov lr, lr, lsr #15 + orr r11, lr, r11, lsl #17 @ C.i + rsb ip, ip, #0 + smull r10, r6, r8, r10 + smlal r10, r6, r9, ip + mov r10, r10, lsr #15 + orr r6, r10, r6, lsl #17 @ C.r + + ldm r0, {r8, r9} @ A + add r10, r8, r6 @ T0.r + sub r8, r8, r6 @ T1.r + add ip, r9, r11 @ T0.i + sub r9, r9, r11 @ T1.i + stmia sp, {r8, r9, r10, ip} @ park T1, T0 + + /* D = tw3 * Fout[3m] */ + ldm r3, {r8, r9} + ldrsh r10, [r5] + ldrsh ip, [r5, #2] + smull lr, r11, r9, r10 + smlal lr, r11, r8, ip + mov lr, lr, lsr #15 + orr r11, lr, r11, lsl #17 @ D.i + rsb ip, ip, #0 + smull r10, r6, r8, r10 + smlal r10, r6, r9, ip + mov r10, r10, lsr #15 + orr r6, r10, r6, lsl #17 @ D.r + add lr, sp, #16 + stmia lr, {r6, r11} @ park D + + /* B = tw1 * Fout[m] */ + ldm r1, {r8, r9} + ldrsh r10, [r4] + ldrsh ip, [r4, #2] + smull lr, r11, r9, r10 + smlal lr, r11, r8, ip + mov lr, lr, lsr #15 + orr r11, lr, r11, lsl #17 @ B.i + rsb ip, ip, #0 + smull r10, r6, r8, r10 + smlal r10, r6, r9, ip + mov r10, r10, lsr #15 + orr r6, r10, r6, lsl #17 @ B.r + add lr, sp, #16 + ldm lr, {r8, r9} @ D back + +/* Common tail: B in (r6, r11), D in (r8, r9), T0 and T1 on the frame. */ +.Lb4_combine: + add r10, r6, r8 @ T2.r = B.r + D.r + sub r6, r6, r8 @ T3.r = B.r - D.r + add ip, r11, r9 @ T2.i + sub r11, r11, r9 @ T3.i + ldr r8, [sp, #8] @ T0.r + ldr r9, [sp, #12] @ T0.i + add r8, r8, r10 @ out0.r = T0.r + T2.r + add r9, r9, ip @ out0.i + sub r10, r8, r10, lsl #1 @ out2.r = out0.r - 2*T2.r + sub ip, r9, ip, lsl #1 @ out2.i + stm r0, {r8, r9} + stm r2, {r10, ip} + ldm sp, {r8, r9} @ T1 + add r10, r8, r11 @ out1.r = T1.r + T3.i + sub ip, r9, r6 @ out1.i = T1.i - T3.r + stm r1, {r10, ip} + sub r10, r8, r11 @ out3.r = T1.r - T3.i + add ip, r9, r6 @ out3.i = T1.i + T3.r + stm r3, {r10, ip} + + add r0, r0, #8 + add r1, r1, #8 + add r2, r2, #8 + add r3, r3, #8 + subs r7, r7, #1 + bne .Lb4_inner + + ldr r5, [sp, #40] @ N + subs r5, r5, #1 + str r5, [sp, #40] + bne .Lb4_outer + add sp, sp, #52 + pop {r4-r11, pc} + .size kf_bfly4_armv4, .-kf_bfly4_armv4 + + +/* ------------------------------------------------------------------------ + * void kf_bfly3_armv4(kiss_fft_cpx *Fout, const kiss_twiddle_cpx *tw, + * int fstride, int m, int N, int mm) + * + * Frame, 36 bytes. The outer-loop half is ordered to match r0-r6 at the end + * of the prologue, so one stm stores it: + * 0 s1.r 4 s1.i 8 Fout 12 tw 16 fstride 20 m 24 N + * 28 mm*8 32 m*8 + * ------------------------------------------------------------------------ */ + .align 2 + +/* Unreachable once the prime factor transform is built, so it gives its IRAM + back rather than sitting resident and unread. Still assembled: a size the + Good-Thomas path does not cover falls through to it, and correct-but-slow + from DRAM is the right failure mode. */ +#if defined(OPUS_ARM_ICODE) && !defined(OPUS_PFA) + .section .icode, "ax", %progbits +#else + .text +#endif + .align 2 + + .global kf_bfly3_armv4 + .type kf_bfly3_armv4, %function +kf_bfly3_armv4: + push {r4-r11, lr} + sub sp, sp, #36 + ldr r4, [sp, #72] @ N + ldr r5, [sp, #76] @ mm + mov r2, r2, lsl #2 @ fstride in bytes + mov r5, r5, lsl #3 + mov r6, r3, lsl #3 @ M + add ip, sp, #8 + stmia ip, {r0-r6} + ldr r7, .Lb3_epi3 + +.Lb3_outer: + ldr r0, [sp, #8] @ Fout for this i + ldr r4, [sp, #28] @ mm*8 + add r4, r0, r4 + str r4, [sp, #8] + ldr r4, [sp, #32] @ M + add r1, r0, r4 @ p1 = Fout + m + add r2, r1, r4 @ p2 = Fout + 2m + ldr r3, [sp, #12] @ tw1 = twiddles + mov r4, r3 @ tw2 = twiddles + ldr r6, [sp, #20] @ inner count = m + + /* Pass 0: twiddles[0] is 1, so it is peeled. */ + ldm r1, {r8, r9} @ s1 = Fout[m] + ldm r2, {r5, r11} @ s2 = Fout[2m] + b .Lb3_combine + +.Lb3_inner: + ldr ip, [sp, #16] @ fstride + add r3, r3, ip @ tw1 += fstride + add r4, r4, ip, lsl #1 @ tw2 += 2*fstride + + /* s1 = tw1 * Fout[m] */ + ldm r1, {r8, r9} + ldrsh r10, [r3] + ldrsh ip, [r3, #2] + smull lr, r11, r9, r10 + smlal lr, r11, r8, ip + mov lr, lr, lsr #15 + orr r11, lr, r11, lsl #17 @ s1.i + rsb ip, ip, #0 + smull r10, r5, r8, r10 + smlal r10, r5, r9, ip + mov r10, r10, lsr #15 + orr r5, r10, r5, lsl #17 @ s1.r + stmia sp, {r5, r11} @ park s1 + + /* s2 = tw2 * Fout[2m] */ + ldm r2, {r8, r9} + ldrsh r10, [r4] + ldrsh ip, [r4, #2] + smull lr, r11, r9, r10 + smlal lr, r11, r8, ip + mov lr, lr, lsr #15 + orr r11, lr, r11, lsl #17 @ s2.i + rsb ip, ip, #0 + smull r10, r5, r8, r10 + smlal r10, r5, r9, ip + mov r10, r10, lsr #15 + orr r5, r10, r5, lsl #17 @ s2.r + ldm sp, {r8, r9} @ s1 back + +/* Common tail: s1 in (r8, r9), s2 in (r5, r11). */ +.Lb3_combine: + add r10, r8, r5 @ s3.r = s1.r + s2.r + sub r8, r8, r5 @ s0.r = s1.r - s2.r + add ip, r9, r11 @ s3.i + sub r9, r9, r11 @ s0.i + + smull r5, r11, r8, r7 @ s0.r *= epi3.i + mov r5, r5, lsr #15 + orr r8, r5, r11, lsl #17 + smull r5, r11, r9, r7 @ s0.i *= epi3.i + mov r5, r5, lsr #15 + orr r9, r5, r11, lsl #17 + + ldm r0, {r5, r11} @ Fout[0] + add r5, r5, r10 @ Fout[0] += s3 + add r11, r11, ip + stm r0, {r5, r11} + /* Recover the pre-add value and take Fout[0] - s3/2 from it. Cheaper + than holding the original in a register we do not have. */ + sub r5, r5, r10 + sub r5, r5, r10, asr #1 + sub r11, r11, ip + sub r11, r11, ip, asr #1 + + add r10, r5, r9 @ Fout[2m].r = t.r + s0.i + sub ip, r11, r8 @ Fout[2m].i = t.i - s0.r + stm r2, {r10, ip} + sub r10, r5, r9 @ Fout[m].r = t.r - s0.i + add ip, r11, r8 @ Fout[m].i = t.i + s0.r + stm r1, {r10, ip} + + add r0, r0, #8 + add r1, r1, #8 + add r2, r2, #8 + subs r6, r6, #1 + bne .Lb3_inner + + ldr r4, [sp, #24] @ N + subs r4, r4, #1 + str r4, [sp, #24] + bne .Lb3_outer + add sp, sp, #36 + pop {r4-r11, pc} +.Lb3_epi3: + .word -28378 + .size kf_bfly3_armv4, .-kf_bfly3_armv4 + +/* ------------------------------------------------------------------------ + * void kf_bfly5_armv4(kiss_fft_cpx *Fout, const kiss_twiddle_cpx *tw, + * int fstride, int m, int N, int mm) + * + * The radix-5 body needs five points plus a complex multiply live at once, + * which no register file this size can hold, so the four transformed points + * are staged through the frame in ldm/stm pairs. What stays resident is all + * the bookkeeping: the output pointer, the point stride, the twiddle base + * and offset, and the counter. Addresses for the other four points and + * their twiddles are derived from those with shifted adds rather than + * carried in registers, trading one cycle each for four registers. + * + * Frame, 68 bytes: + * 0 s7.r 4 s7.i 8 s10.r 12 s10.i 16 s9.r 20 s9.i + * 32 s5.r 36 s5.i 40 s11.r 44 s11.i + * 48 Fout 52 m 56 N 60 mm*8 64 fstride + * The outer-loop half is ordered so r0, r3, r5, r6 and r7 store with one stm. + * ------------------------------------------------------------------------ */ + .align 2 + .global kf_bfly5_armv4 + .type kf_bfly5_armv4, %function + +/* One complex multiply. Data in r6 (real) and r7 (imag), twiddle real in r8 + and imag in ip, lr as the 64-bit low word. Results land in \mr and \mi. + The register choices satisfy the ARMv4 rule that RdHi, RdLo and Rm differ; + the second smull deliberately reuses Rs as RdLo, exactly as the C_MUL asm + in kiss_fft_armv4.h does. */ + .macro CMUL5 mr, mi + smull lr, \mi, r7, r8 + smlal lr, \mi, r6, ip + mov lr, lr, lsr #15 + orr \mi, lr, \mi, lsl #17 + rsb ip, ip, #0 + smull r8, \mr, r6, r8 + smlal r8, \mr, r7, ip + mov r8, r8, lsr #15 + orr \mr, r8, \mr, lsl #17 + .endm + +kf_bfly5_armv4: + push {r4-r11, lr} + sub sp, sp, #68 + ldr r5, [sp, #104] @ N + ldr r6, [sp, #108] @ mm + mov r7, r2, lsl #2 @ fstride in bytes + mov r2, r1 @ twiddle base + mov r1, r3, lsl #3 @ M = m * sizeof(kiss_fft_cpx) + mov r6, r6, lsl #3 + add ip, sp, #48 + stmia ip, {r0, r3, r5, r6, r7} + +.Lb5_outer: + ldr r0, [sp, #48] @ Fout for this i + ldr r6, [sp, #60] @ mm*8 + add r6, r0, r6 + str r6, [sp, #48] @ Fout for i+1 + ldr r5, [sp, #52] @ inner count = m + mov r3, #0 @ twiddle offset + + /* Pass 0: every twiddle is 1, so the four points are used as they are. */ + add ip, r0, r1 + ldm ip, {r4, r11} @ s1 + add ip, r0, r1, lsl #2 + ldm ip, {r9, r10} @ s4 + add r6, r4, r9 @ s7.r = s1.r + s4.r + add r8, r11, r10 @ s7.i + sub r4, r4, r9 @ s10.r = s1.r - s4.r + sub r11, r11, r10 @ s10.i + stmia sp, {r6, r8} + add ip, sp, #8 + stmia ip, {r4, r11} + add ip, r0, r1, lsl #1 + ldm ip, {r4, r11} @ s2 + add ip, r0, r1 + add ip, ip, r1, lsl #1 + ldm ip, {r9, r10} @ s3 + add r6, r4, r9 @ s8.r = s2.r + s3.r + add r8, r11, r10 @ s8.i + sub r4, r4, r9 @ s9.r = s2.r - s3.r + sub r11, r11, r10 @ s9.i + add ip, sp, #16 + stmia ip, {r4, r11} + b .Lb5_combine + +.Lb5_inner: + ldr ip, [sp, #64] @ fstride, the one loop-invariant reload + add r3, r3, ip @ twiddle offset for this pass + + /* s1 = tw[u] * Fout[m] */ + add ip, r0, r1 + ldm ip, {r6, r7} + add ip, r2, r3 + ldrsh r8, [ip] + ldrsh ip, [ip, #2] + CMUL5 r4, r11 + + /* s4 = tw[4u] * Fout[4m] */ + add ip, r0, r1, lsl #2 + ldm ip, {r6, r7} + add ip, r2, r3, lsl #2 + ldrsh r8, [ip] + ldrsh ip, [ip, #2] + CMUL5 r9, r10 + + add r6, r4, r9 @ s7.r + add r8, r11, r10 @ s7.i + sub r4, r4, r9 @ s10.r + sub r11, r11, r10 @ s10.i + stmia sp, {r6, r8} + add ip, sp, #8 + stmia ip, {r4, r11} + + /* s2 = tw[2u] * Fout[2m] */ + add ip, r0, r1, lsl #1 + ldm ip, {r6, r7} + add ip, r2, r3, lsl #1 + ldrsh r8, [ip] + ldrsh ip, [ip, #2] + CMUL5 r4, r11 + + /* s3 = tw[3u] * Fout[3m] */ + add ip, r0, r1 + add ip, ip, r1, lsl #1 + ldm ip, {r6, r7} + add ip, r2, r3 + add ip, ip, r3, lsl #1 + ldrsh r8, [ip] + ldrsh ip, [ip, #2] + CMUL5 r9, r10 + + add r6, r4, r9 @ s8.r + add r8, r11, r10 @ s8.i + sub r4, r4, r9 @ s9.r + sub r11, r11, r10 @ s9.i + add ip, sp, #16 + stmia ip, {r4, r11} + +/* Common tail: s8 in (r6, r8); s7, s10 and s9 on the frame. */ +.Lb5_combine: + ldm sp, {r9, r10} @ s7 + add r4, r9, r6 @ sum = s7 + s8 + add r11, r10, r8 + sub r9, r9, r6 @ diff = s7 - s8 + sub r10, r10, r8 + + ldm r0, {r6, r7} @ Fout[0] + add r6, r6, r4 @ Fout[0] += sum + add r7, r7, r11 + stm r0, {r6, r7} + /* Recover the pre-add value; the term common to both output pairs is + Fout[0] - sum/4, and QUARTER_OF rounds rather than truncating. */ + sub r6, r6, r4 + sub r7, r7, r11 + add r4, r4, #2 + add r11, r11, #2 + sub r6, r6, r4, asr #2 @ t.r + sub r7, r7, r11, asr #2 @ t.i + + /* diff *= yc, where yc = sqrt(5)/4 absorbs the four cosine products. */ + ldr ip, .Lb5_yc + smull r4, r8, r9, ip + mov r4, r4, lsr #15 + orr r9, r4, r8, lsl #17 + smull r4, r8, r10, ip + mov r4, r4, lsr #15 + orr r10, r4, r8, lsl #17 + + add r4, r6, r9 @ s5 = t + diff*yc + add r8, r7, r10 + sub r6, r6, r9 @ s11 = t - diff*yc + sub r7, r7, r10 + add ip, sp, #32 + stmia ip, {r4, r8} + add ip, sp, #40 + stmia ip, {r6, r7} + + /* The sine terms. Each output component is a pair of products over the + same operands, so they accumulate into one smull/smlal instead of + narrowing twice. */ + add ip, sp, #8 + ldm ip, {r6, r7} @ s10 + add ip, sp, #16 + ldm ip, {r8, r9} @ s9 + ldr r10, .Lb5_yai + ldr r11, .Lb5_ybi + + smull lr, ip, r7, r10 @ s6.r = s10.i*ya.i + s9.i*yb.i + smlal lr, ip, r9, r11 + mov lr, lr, lsr #15 + orr r4, lr, ip, lsl #17 + + rsb r11, r11, #0 + smull lr, ip, r9, r10 @ s12.r = s9.i*ya.i - s10.i*yb.i + smlal lr, ip, r7, r11 + mov lr, lr, lsr #15 + orr r7, lr, ip, lsl #17 + rsb r11, r11, #0 + + smull lr, ip, r6, r10 @ s6.i = -(s10.r*ya.i + s9.r*yb.i) + smlal lr, ip, r8, r11 + mov lr, lr, lsr #15 + orr ip, lr, ip, lsl #17 + rsb r9, ip, #0 + + rsb r10, r10, #0 + smull lr, ip, r6, r11 @ s12.i = s10.r*yb.i - s9.r*ya.i + smlal lr, ip, r8, r10 + mov lr, lr, lsr #15 + orr r10, lr, ip, lsl #17 + + add ip, sp, #32 + ldm ip, {r6, r8} @ s5 + sub r11, r6, r4 @ Fout[m] = s5 - s6 + sub ip, r8, r9 + add r4, r6, r4 @ Fout[4m] = s5 + s6 + add r9, r8, r9 + add lr, r0, r1 + stmia lr, {r11, ip} + add lr, r0, r1, lsl #2 + stmia lr, {r4, r9} + + add ip, sp, #40 + ldm ip, {r6, r8} @ s11 + add r11, r6, r7 @ Fout[2m] = s11 + s12 + add ip, r8, r10 + sub r7, r6, r7 @ Fout[3m] = s11 - s12 + sub r10, r8, r10 + add lr, r0, r1, lsl #1 + stmia lr, {r11, ip} + add lr, r0, r1 + add lr, lr, r1, lsl #1 + stmia lr, {r7, r10} + + add r0, r0, #8 + subs r5, r5, #1 + bne .Lb5_inner + + ldr r6, [sp, #56] @ N + subs r6, r6, #1 + str r6, [sp, #56] + bne .Lb5_outer + add sp, sp, #68 + pop {r4-r11, pc} +.Lb5_yc: + .word 18318 @ sqrt(5)/4 in Q15 +.Lb5_yai: + .word -31164 @ -sin(2*pi/5) +.Lb5_ybi: + .word -19261 @ -sin(4*pi/5) + .size kf_bfly5_armv4, .-kf_bfly5_armv4 + + .section .note.GNU-stack,"",%progbits diff --git a/lib/rbcodec/codecs/libopus/celt/arm/kiss_fft_armv5e.h b/lib/rbcodec/codecs/libopus/celt/arm/kiss_fft_armv5e.h index 9eca183d77..3fc1e2d823 100644 --- a/lib/rbcodec/codecs/libopus/celt/arm/kiss_fft_armv5e.h +++ b/lib/rbcodec/codecs/libopus/celt/arm/kiss_fft_armv5e.h @@ -113,6 +113,33 @@ } \ while(0) +/* Assembly radix-3, radix-4 and radix-5 passes, in kiss_fft_armv5e_asm.S. + The C_MUL above is already as tight as the core allows; what the assembly + recovers is the bookkeeping gcc cannot keep resident. Measured over eight + packets of stereo decode, 16.0% of the instructions executed in the + compiled radix-4 pass are stack traffic, 11.4% of radix-3 and 23.0% of + radix-5, almost all of it output pointers spilled and reloaded around the + complex multiplies. Radix-2 is left in C: it needs no twiddle pointers + and spills only 4.7%, so there is nothing to win. The kernels take + st->twiddles rather than st, so the assembly needs no knowledge of the + layout of kiss_fft_state. */ +#ifndef OPUS_ARM_NO_FFT_ASM +#define OVERRIDE_kf_bfly3 +#define OVERRIDE_kf_bfly4 +#define OVERRIDE_kf_bfly5 + +void kf_bfly3_armv5e(kiss_fft_cpx *Fout, const kiss_twiddle_cpx *tw, + int fstride, int m, int N, int mm); +void kf_bfly4_armv5e(kiss_fft_cpx *Fout, const kiss_twiddle_cpx *tw, + int fstride, int m, int N, int mm); +void kf_bfly5_armv5e(kiss_fft_cpx *Fout, const kiss_twiddle_cpx *tw, + int fstride, int m, int N, int mm); + +#define kf_bfly3(Fout, fstride, st, m, N, mm) kf_bfly3_armv5e((Fout), (st)->twiddles, (int)(fstride), (m), (N), (mm)) +#define kf_bfly4(Fout, fstride, st, m, N, mm) kf_bfly4_armv5e((Fout), (st)->twiddles, (int)(fstride), (m), (N), (mm)) +#define kf_bfly5(Fout, fstride, st, m, N, mm) kf_bfly5_armv5e((Fout), (st)->twiddles, (int)(fstride), (m), (N), (mm)) +#endif /* OPUS_ARM_NO_FFT_ASM */ + #endif /* FIXED_POINT */ #endif /* KISS_FFT_GUTS_H */ diff --git a/lib/rbcodec/codecs/libopus/celt/arm/kiss_fft_armv5e_asm.S b/lib/rbcodec/codecs/libopus/celt/arm/kiss_fft_armv5e_asm.S new file mode 100644 index 0000000000..ec4e8f819c --- /dev/null +++ b/lib/rbcodec/codecs/libopus/celt/arm/kiss_fft_armv5e_asm.S @@ -0,0 +1,595 @@ +/* ARMv5E FFT butterflies for the CELT fixed-point kiss_fft. + * + * Copyright (c) 2003-2004, Mark Borgerding + * Copyright (c) 2005-2007, Xiph.Org Foundation + * Copyright (c) 2026 Michael Giacomelli + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the conditions stated in + * celt/kiss_fft.c are met. + * + * Why this exists + * --------------- + * The argument here is not the one behind kiss_fft_armv4_asm.S. On + * ARM926EJ-S a load costs one cycle and a load multiple of n costs n, so + * bursting buys nothing; upstream's arm/kiss_fft_armv5e.h already supplies a + * packed-twiddle complex multiply that is four DSP multiplies and a + * subtraction, which is optimal. What is left is bookkeeping. + * + * Measured over eight packets of stereo decode, 16.0% of the instructions + * executed in the compiled radix-4 pass are stack traffic: gcc cannot hold + * four output pointers, a twiddle base and a stride at once while a complex + * multiply is in flight, so it spills the pointers and reloads them every + * pass. The radix-3 pass loses 11.4% the same way and the radix-5 pass + * 23.0%. + * + * These routines keep the bookkeeping resident and park only what genuinely + * will not fit. Three things pay for that: + * + * - The twiddle addresses are a base and a byte displacement rather than + * three running pointers, so one register holds what three did and the + * stride never has to be reloaded to advance them. Pass j reads + * twiddles[j*fstride] as [base, disp], twiddles[2*j*fstride] as + * [base, disp, lsl #1], and only the radix-4 third twiddle needs an + * explicit 3*disp. + * + * - Every store is post-indexed, so the four output pointers advance for + * free and the loop needs no pointer arithmetic at all. + * + * - The Q15 doubling that C_MUL applies to each product is deferred into + * the add or subtract that consumes it, as an immediate shift, which is + * free on this core. Deferring is exact: SHL32 is a wrapping shift, so + * 2*(a+b) and 2*a+2*b agree in all 32 bits. + * + * Every pair load is ordered so the register the next instruction needs + * landed at least two instructions earlier, which is what the five-stage + * pipeline wants; the loop counter's decrement fills the one remaining slot. + * + * Bit-exactness. The multiply sequence is the one in + * arm/kiss_fft_armv5e.h -- smulwb, smulwb, smulwt, smlawt, then a + * subtraction -- so every product truncates exactly where the C did, and the + * deferred doubling is exact as above. Output is bit-identical to the C + * path, which the test harness checks sample by sample. + */ + +#if defined(__thumb__) || defined(__thumb2__) +#error "kiss_fft_armv5e_asm.S must be assembled in ARM mode" +#endif + + .text + .align 2 + +/* C_MUL, packed twiddle. + * + * \pr, \pi the point, .r and .i, consumed + * \tw the packed twiddle: tw.r in bits 15:0, tw.i in 31:16 + * \t scratch, consumed + * result \pr = m.r/2, \pi = m.i/2 (the caller doubles as it consumes) + * + * Matches C_MUL in arm/kiss_fft_armv5e.h term for term: + * m.r = ((p.r*tw.r)>>16 - (p.i*tw.i)>>16) << 1 + * m.i = ((p.i*tw.r)>>16 + (p.r*tw.i)>>16) << 1 + */ + .macro CMUL_HALF pr, pi, tw, t + smulwt \t, \pi, \tw @ p.i*tw.i >> 16 + smulwb \pi, \pi, \tw @ p.i*tw.r >> 16 + smlawt \pi, \pr, \tw, \pi @ + p.r*tw.i >> 16 -> m.i/2 + smulwb \pr, \pr, \tw @ p.r*tw.r >> 16 + sub \pr, \pr, \t @ - p.i*tw.i >> 16 -> m.r/2 + .endm + + +/* ------------------------------------------------------------------------ + * void kf_bfly4_armv5e(kiss_fft_cpx *Fout, const kiss_twiddle_cpx *tw, + * int fstride, int m, int N, int mm) + * + * r0 = Fout r1 = tw r2 = fstride r3 = m [sp+0] = N [sp+4] = mm + * + * Inner-loop registers: + * r0-r3 the four output pointers, Fout + 0, m, 2m, 3m, post-incremented + * r4 twiddle base r5 twiddle displacement in bytes + * r6 pass counter r7-r11, ip, lr scratch + * + * Frame, 40 bytes. The first sixteen are the placed spill: the inner loop + * parks one complex value there, the peeled pass two. + * 0 v.r / T0.r 4 v.i / T1.r 8 T1.i 12 T0.i + * 16 fstride, bytes 20 Fout for the next i 24 N left + * 28 mm*8 32 M = m*8 36 m + * ------------------------------------------------------------------------ */ + .global kf_bfly4_armv5e + .type kf_bfly4_armv5e, %function +kf_bfly4_armv5e: + push {r4-r11, lr} + cmp r3, #1 + bne .Lb4_general + +/* Degenerate case: every twiddle is 1, so four consecutive complex points -- + eight consecutive words -- go in and eight come out with no multiplies. + This is the one radix-4 body that fits entirely in registers. */ + ldr r1, [sp, #36] @ N +.Lb4_m1: + ldm r0, {r4, r5, r6, r7, r8, r9, r10, r11} + sub ip, r4, r8 @ s0.r = F0.r - F2.r + sub lr, r5, r9 @ s0.i + add r4, r4, r8 @ F0.r += F2.r + add r5, r5, r9 + add r8, r6, r10 @ s1.r = F1.r + F3.r + add r9, r7, r11 @ s1.i + sub r6, r6, r10 @ d.r = F1.r - F3.r + sub r7, r7, r11 @ d.i + sub r10, r4, r8 @ F2.r = F0.r - s1.r + sub r11, r5, r9 @ F2.i + add r4, r4, r8 @ F0.r += s1.r + add r5, r5, r9 + add r8, ip, r7 @ F1.r = s0.r + d.i + sub r9, lr, r6 @ F1.i = s0.i - d.r + sub ip, ip, r7 @ F3.r = s0.r - d.i + add lr, lr, r6 @ F3.i = s0.i + d.r + stmia r0!, {r4, r5, r8, r9, r10, r11, ip, lr} + subs r1, r1, #1 + bne .Lb4_m1 + pop {r4-r11, pc} + +.Lb4_general: + sub sp, sp, #40 + ldr r7, [sp, #76] @ N + ldr ip, [sp, #80] @ mm + mov r2, r2, lsl #2 @ fstride in bytes, 4 per kiss_twiddle_cpx + mov ip, ip, lsl #3 @ mm * sizeof(kiss_fft_cpx) + mov lr, r3, lsl #3 @ M = m * sizeof(kiss_fft_cpx) + str r2, [sp, #16] + str r0, [sp, #20] + str r7, [sp, #24] + str ip, [sp, #28] + str lr, [sp, #32] + str r3, [sp, #36] + mov r4, r1 @ twiddle base, invariant + +.Lb4_outer: + ldr r0, [sp, #20] @ Fout for this i + ldr r7, [sp, #28] @ mm*8 + ldr ip, [sp, #32] @ M + add r7, r0, r7 + str r7, [sp, #20] @ Fout for i+1 + add r1, r0, ip @ p1 = Fout + m + add r2, r1, ip @ p2 = Fout + 2m + add r3, r2, ip @ p3 = Fout + 3m + ldr r6, [sp, #36] @ pass counter = m + mov r5, #0 @ twiddle displacement + +/* Pass 0 twiddles by twiddles[0], which is 1, so it is peeled: no twiddle + load and no multiply. In fixed point twiddles[0] is 32767 rather than + 32768, so skipping it also drops a small systematic gain error. These + values are full scale, not the halved form the inner loop carries, so the + combine here has no doubling in it. */ + ldr r8, [r0] @ A.r + ldr r9, [r0, #4] @ A.i + ldr r10, [r2] @ C.r + ldr r11, [r2, #4] @ C.i + add r7, r8, r10 @ T0.r = A.r + C.r + sub r8, r8, r10 @ T1.r = A.r - C.r + add r10, r9, r11 @ T0.i + sub r9, r9, r11 @ T1.i + str r7, [sp, #0] + str r8, [sp, #4] + str r9, [sp, #8] + str r10, [sp, #12] + ldr r8, [r1] @ B.r + ldr r9, [r1, #4] @ B.i + ldr r10, [r3] @ D.r + ldr r11, [r3, #4] @ D.i + add r7, r8, r10 @ u.r = B.r + D.r + sub r8, r8, r10 @ v.r = B.r - D.r + add r10, r9, r11 @ u.i + sub r9, r9, r11 @ v.i + ldr r11, [sp, #0] @ T0.r + ldr ip, [sp, #12] @ T0.i + add r11, r11, r7 @ out0.r = T0.r + u.r + sub r7, r11, r7, lsl #1 @ out2.r = T0.r - u.r + add ip, ip, r10 @ out0.i + sub r10, ip, r10, lsl #1 @ out2.i + str r11, [r0], #4 + str ip, [r0], #4 + str r7, [r2], #4 + str r10, [r2], #4 + ldr r11, [sp, #4] @ T1.r + ldr ip, [sp, #8] @ T1.i + add r7, r11, r9 @ out1.r = T1.r + v.i + sub r10, ip, r8 @ out1.i = T1.i - v.r + str r7, [r1], #4 + str r10, [r1], #4 + sub r7, r11, r9 @ out3.r = T1.r - v.i + add r10, ip, r8 @ out3.i = T1.i + v.r + str r7, [r3], #4 + str r10, [r3], #4 + subs r6, r6, #1 + beq .Lb4_next_i + +/* Passes 1 .. m-1. D and B are twiddled first and folded straight into + u = (B+D)/2 and v = (B-D)/2, which frees the multiply's five registers + again before C needs them; only v has to be parked. The counter's + decrement sits at the top to cover the one load-use slot that had nothing + else to fill it, and nothing between there and the branch touches the + flags. */ +.Lb4_inner: + ldr r7, [sp, #16] @ fstride, bytes + subs r6, r6, #1 + add r5, r5, r7 @ next twiddle displacement + add r7, r5, r5, lsl #1 @ 3 * displacement + ldr r7, [r4, r7] @ tw3, packed + ldr r9, [r3, #4] @ D.i + ldr r8, [r3] @ D.r + CMUL_HALF r8, r9, r7, r10 @ D.r/2 -> r8, D.i/2 -> r9 + ldr r7, [r4, r5] @ tw1, packed + ldr r11, [r1, #4] @ B.i + ldr r10, [r1] @ B.r + CMUL_HALF r10, r11, r7, ip @ B.r/2 -> r10, B.i/2 -> r11 + add r7, r10, r8 @ u.r = (B.r + D.r)/2 + sub r8, r10, r8 @ v.r + add r10, r11, r9 @ u.i + sub r9, r11, r9 @ v.i + str r8, [sp, #0] @ park v.r + str r9, [sp, #4] @ park v.i + ldr r8, [r4, r5, lsl #1] @ tw2, packed + ldr r11, [r2, #4] @ C.i + ldr r9, [r2] @ C.r + CMUL_HALF r9, r11, r8, ip @ C.r/2 -> r9, C.i/2 -> r11 + ldr r8, [r0] @ A.r + ldr ip, [r0, #4] @ A.i + add lr, r8, r9, lsl #1 @ T0.r = A.r + C.r + sub r8, r8, r9, lsl #1 @ T1.r + add r9, ip, r11, lsl #1 @ T0.i + sub ip, ip, r11, lsl #1 @ T1.i + add lr, lr, r7, lsl #1 @ out0.r = T0.r + u.r + sub r7, lr, r7, lsl #2 @ out2.r = T0.r - u.r + add r9, r9, r10, lsl #1 @ out0.i + sub r10, r9, r10, lsl #2 @ out2.i + str lr, [r0], #4 + str r9, [r0], #4 + str r7, [r2], #4 + str r10, [r2], #4 + ldr r11, [sp, #4] @ v.i + ldr lr, [sp, #0] @ v.r + add r7, r8, r11, lsl #1 @ out1.r = T1.r + v.i + sub r9, ip, lr, lsl #1 @ out1.i = T1.i - v.r + str r7, [r1], #4 + str r9, [r1], #4 + sub r7, r8, r11, lsl #1 @ out3.r = T1.r - v.i + add r9, ip, lr, lsl #1 @ out3.i = T1.i + v.r + str r7, [r3], #4 + str r9, [r3], #4 + bne .Lb4_inner + +.Lb4_next_i: + ldr r7, [sp, #24] @ N left + subs r7, r7, #1 + str r7, [sp, #24] + bne .Lb4_outer + add sp, sp, #40 + pop {r4-r11, pc} + .size kf_bfly4_armv5e, .-kf_bfly4_armv5e + + +/* ------------------------------------------------------------------------ + * void kf_bfly3_armv5e(kiss_fft_cpx *Fout, const kiss_twiddle_cpx *tw, + * int fstride, int m, int N, int mm) + * + * Inner-loop registers: + * r0-r2 the three output pointers, Fout + 0, m, 2m, post-incremented + * r4 twiddle base r5 twiddle displacement in bytes + * r6 pass counter r7 epi3.i, in the low halfword + * r3, r8-r11, ip, lr scratch + * + * Frame, 24 bytes; this kernel parks nothing, so all of it is outer-loop + * state read once per i. + * 0 fstride, bytes 4 Fout for the next i 8 N left + * 12 mm*8 16 M = m*8 20 m + * + * The peeled pass and the twiddled passes share one body: the peel drops B + * and C in straight from memory at full scale, and the twiddled path doubles + * the halved products before falling through to the same code. + * ------------------------------------------------------------------------ */ + .align 2 + .global kf_bfly3_armv5e + .type kf_bfly3_armv5e, %function +kf_bfly3_armv5e: + push {r4-r11, lr} + sub sp, sp, #24 + ldr r6, [sp, #60] @ N + ldr ip, [sp, #64] @ mm + mov r2, r2, lsl #2 @ fstride in bytes + mov ip, ip, lsl #3 @ mm * sizeof(kiss_fft_cpx) + mov lr, r3, lsl #3 @ M = m * sizeof(kiss_fft_cpx) + str r2, [sp, #0] + str r0, [sp, #4] + str r6, [sp, #8] + str ip, [sp, #12] + str lr, [sp, #16] + str r3, [sp, #20] + mov r4, r1 @ twiddle base, invariant + ldr r7, .Lb3_epi3 @ only the low halfword is read by smulwb + +.Lb3_outer: + ldr r0, [sp, #4] @ Fout for this i + ldr r3, [sp, #12] @ mm*8 + ldr ip, [sp, #16] @ M + add r3, r0, r3 + str r3, [sp, #4] @ Fout for i+1 + add r1, r0, ip @ p1 = Fout + m + add r2, r1, ip @ p2 = Fout + 2m + ldr r6, [sp, #20] @ pass counter = m + mov r5, #0 @ twiddle displacement + +/* Pass 0 twiddles by twiddles[0], which is 1: B and C are the stored values + at full scale, so it enters the body with no multiply. */ + ldr r8, [r1] @ B.r + ldr r9, [r1, #4] @ B.i + ldr r10, [r2] @ C.r + ldr r11, [r2, #4] @ C.i + b .Lb3_body + +.Lb3_pass: + ldr r3, [r4, r5] @ tw1, packed + ldr r9, [r1, #4] @ B.i + ldr r8, [r1] @ B.r + CMUL_HALF r8, r9, r3, r10 + ldr r3, [r4, r5, lsl #1] @ tw2, packed + ldr r11, [r2, #4] @ C.i + ldr r10, [r2] @ C.r + CMUL_HALF r10, r11, r3, ip + mov r8, r8, lsl #1 @ B.r + mov r9, r9, lsl #1 @ B.i + mov r10, r10, lsl #1 @ C.r + mov r11, r11, lsl #1 @ C.i + +.Lb3_body: + add r3, r8, r10 @ s3.r = B.r + C.r + sub r8, r8, r10 @ s0.r = B.r - C.r + add ip, r9, r11 @ s3.i + sub r9, r9, r11 @ s0.i + ldr r10, [r0] @ A.r + ldr r11, [r0, #4] @ A.i + sub lr, r10, r3, asr #1 @ A.r - HALF_OF(s3.r) + add r10, r10, r3 @ Fout0.r = A.r + s3.r + str r10, [r0], #4 + sub r3, r11, ip, asr #1 @ A.i - HALF_OF(s3.i) + add r11, r11, ip @ Fout0.i + str r11, [r0], #4 + smulwb r10, r8, r7 @ (s0.r * epi3.i) >> 16 + smulwb r11, r9, r7 @ (s0.i * epi3.i) >> 16 + add ip, lr, r11, lsl #1 @ Fout2.r = h.r + s0'.i + sub r8, r3, r10, lsl #1 @ Fout2.i = h.i - s0'.r + str ip, [r2], #4 + str r8, [r2], #4 + sub ip, lr, r11, lsl #1 @ Fout1.r = h.r - s0'.i + add r8, r3, r10, lsl #1 @ Fout1.i = h.i + s0'.r + str ip, [r1], #4 + str r8, [r1], #4 + ldr r3, [sp, #0] @ fstride, bytes + subs r6, r6, #1 + add r5, r5, r3 @ displacement for the next pass + bne .Lb3_pass + + ldr r3, [sp, #8] @ N left + subs r3, r3, #1 + str r3, [sp, #8] + bne .Lb3_outer + add sp, sp, #24 + pop {r4-r11, pc} +.Lb3_epi3: + .word -28378 + .size kf_bfly3_armv5e, .-kf_bfly3_armv5e + + +/* ------------------------------------------------------------------------ + * void kf_bfly5_armv5e(kiss_fft_cpx *Fout, const kiss_twiddle_cpx *tw, + * int fstride, int m, int N, int mm) + * + * Inner-loop registers: + * r0-r4 the five output pointers, Fout + 0, m, 2m, 3m, 4m, post-incremented + * r5 twiddle base r6 twiddle displacement in bytes + * r7-r11, ip, lr scratch + * + * There is no pass counter: the displacement itself is the induction + * variable, and the loop ends when it reaches m*fstride. That buys back the + * one register this kernel could not otherwise find. + * + * Frame, 56 bytes. The first thirty-two are the placed spill. Five complex + * values will not fit in seven scratch registers at once, so four of them are + * parked at the point where they stop being needed and fetched back one + * component at a time. + * 0 s10.r 4 s10.i 8 s7.r 12 s7.i + * 16 s9.r 20 s9.i 24 s11.r 28 s11.i + * 32 fstride, bytes 36 limit = m*fstride 40 Fout for the next i + * 44 N left 48 mm*8 52 M = m*8 + * + * The eight products that form scratch[6] and scratch[12] all multiply by + * ya.i or yb.i, so both constants live packed in one register and are picked + * apart by smulwb and smulwt. Two of the four sums then collapse into a + * single smulwb and smlawt pair rather than two multiplies, two shifts and + * an add. + * + * The outputs split cleanly: the real parts of all four need only the + * imaginary halves of scratch[9] and scratch[10], and the imaginary parts + * need only the real halves. Doing them in that order halves how much has + * to be live at once. + * ------------------------------------------------------------------------ */ + +/* s7 = P + Q and s10 = P - Q, for P in (r8,r9) and Q in (r10,r11), then park + both. Used twice: once after the twiddled pair, once for the peeled pass. */ + .macro B5_FOLD1 + add r7, r8, r10 @ s7.r + sub r8, r8, r10 @ s10.r + add ip, r9, r11 @ s7.i + sub r9, r9, r11 @ s10.i + str r8, [sp, #0] + str r9, [sp, #4] + str r7, [sp, #8] + str ip, [sp, #12] + .endm + + .align 2 + .global kf_bfly5_armv5e + .type kf_bfly5_armv5e, %function +kf_bfly5_armv5e: + push {r4-r11, lr} + sub sp, sp, #56 + ldr r7, [sp, #92] @ N + ldr ip, [sp, #96] @ mm + mov r2, r2, lsl #2 @ fstride in bytes + mov ip, ip, lsl #3 @ mm * sizeof(kiss_fft_cpx) + mov lr, r3, lsl #3 @ M = m * sizeof(kiss_fft_cpx) + mul r3, r2, r3 @ limit: the displacement after m passes + str r2, [sp, #32] + str r3, [sp, #36] + str r0, [sp, #40] + str r7, [sp, #44] + str ip, [sp, #48] + str lr, [sp, #52] + mov r5, r1 @ twiddle base, invariant + +.Lb5_outer: + ldr r0, [sp, #40] @ Fout for this i + ldr r7, [sp, #48] @ mm*8 + ldr ip, [sp, #52] @ M + add r7, r0, r7 + str r7, [sp, #40] @ Fout for i+1 + add r1, r0, ip @ Fout1 + add r2, r1, ip @ Fout2 + add r3, r2, ip @ Fout3 + add r4, r3, ip @ Fout4 + mov r6, #0 @ twiddle displacement + +/* Pass 0 twiddles by twiddles[0], which is 1, so the four points come in + from memory at full scale with no multiply. */ + ldr r8, [r1] @ s1.r + ldr r9, [r1, #4] @ s1.i + ldr r10, [r4] @ s4.r + ldr r11, [r4, #4] @ s4.i + B5_FOLD1 + ldr r8, [r2] @ s2.r + ldr r9, [r2, #4] @ s2.i + ldr r10, [r3] @ s3.r + ldr r11, [r3, #4] @ s3.i + b .Lb5_fold2 + +.Lb5_pass: + ldr r7, [r5, r6] @ tw1, packed + ldr r9, [r1, #4] @ s1.i + ldr r8, [r1] @ s1.r + CMUL_HALF r8, r9, r7, r10 + ldr r7, [r5, r6, lsl #2] @ tw4, packed + ldr r11, [r4, #4] @ s4.i + ldr r10, [r4] @ s4.r + CMUL_HALF r10, r11, r7, ip + mov r8, r8, lsl #1 @ s1.r + mov r9, r9, lsl #1 @ s1.i + mov r10, r10, lsl #1 @ s4.r + mov r11, r11, lsl #1 @ s4.i + B5_FOLD1 + ldr r7, [r5, r6, lsl #1] @ tw2, packed + ldr r9, [r2, #4] @ s2.i + ldr r8, [r2] @ s2.r + CMUL_HALF r8, r9, r7, r10 + add r7, r6, r6, lsl #1 @ 3 * displacement + ldr r7, [r5, r7] @ tw3, packed + ldr r11, [r3, #4] @ s3.i + ldr r10, [r3] @ s3.r + CMUL_HALF r10, r11, r7, ip + mov r8, r8, lsl #1 @ s2.r + mov r9, r9, lsl #1 @ s2.i + mov r10, r10, lsl #1 @ s3.r + mov r11, r11, lsl #1 @ s3.i + +.Lb5_fold2: + add r7, r8, r10 @ s8.r = s2.r + s3.r + sub r8, r8, r10 @ s9.r + add ip, r9, r11 @ s8.i + sub r9, r9, r11 @ s9.i + str r8, [sp, #16] @ park s9.r + str r9, [sp, #20] @ park s9.i + ldr r10, [sp, #8] @ s7.r + ldr r11, [sp, #12] @ s7.i + add r8, r10, r7 @ p.r = s7.r + s8.r + sub r10, r10, r7 @ q.r + add r9, r11, ip @ p.i + sub r11, r11, ip @ q.i + + ldr r7, [r0] @ A.r + ldr ip, [r0, #4] @ A.i + add lr, r8, #2 + sub lr, r7, lr, asr #2 @ t.r = A.r - QUARTER_OF(p.r) + add r7, r7, r8 @ Fout0.r = A.r + p.r + str r7, [r0], #4 + add r8, r9, #2 + sub r8, ip, r8, asr #2 @ t.i + add ip, ip, r9 @ Fout0.i + str ip, [r0], #4 + + ldr r7, .Lb5_yc + smulwb r9, r10, r7 @ (q.r * yc) >> 16 + smulwb ip, r11, r7 @ (q.i * yc) >> 16 + add r10, lr, r9, lsl #1 @ s5.r = t.r + scratch[4].r + sub r11, lr, r9, lsl #1 @ s11.r = t.r - scratch[4].r + add r7, r8, ip, lsl #1 @ s5.i + sub r9, r8, ip, lsl #1 @ s11.i + str r11, [sp, #24] @ park s11.r + str r9, [sp, #28] @ park s11.i + +/* Real halves. s6.r and s12.r need only the imaginary halves of s9 and + s10, so nothing else has to stay live here. */ + ldr r8, [sp, #4] @ s10.i + ldr r9, [sp, #20] @ s9.i + ldr r11, .Lb5_yab @ ya.i in 15:0, yb.i in 31:16 + smulwb ip, r8, r11 @ s10.i * ya.i + smlawt ip, r9, r11, ip @ + s9.i * yb.i -> s6.r / 2 + smulwb lr, r9, r11 @ s9.i * ya.i + smulwt r9, r8, r11 @ s10.i * yb.i + sub lr, lr, r9 @ -> s12.r / 2 + sub r8, r10, ip, lsl #1 @ Fout1.r = s5.r - s6.r + add r9, r10, ip, lsl #1 @ Fout4.r = s5.r + s6.r + ldr r10, [sp, #24] @ s11.r + str r8, [r1], #4 + str r9, [r4], #4 + add r8, r10, lr, lsl #1 @ Fout2.r = s11.r + s12.r + sub r9, r10, lr, lsl #1 @ Fout3.r + str r8, [r2], #4 + str r9, [r3], #4 + +/* Imaginary halves. s6.i is the negation of the same shape, so the two + stores swap their add and subtract rather than paying for an rsb. */ + ldr r8, [sp, #0] @ s10.r + ldr r9, [sp, #16] @ s9.r + ldr r11, .Lb5_yab + smulwb ip, r8, r11 @ s10.r * ya.i + smlawt ip, r9, r11, ip @ + s9.r * yb.i -> minus s6.i / 2 + smulwt lr, r8, r11 @ s10.r * yb.i + smulwb r9, r9, r11 @ s9.r * ya.i + sub lr, lr, r9 @ -> s12.i / 2 + add r8, r7, ip, lsl #1 @ Fout1.i = s5.i - s6.i + sub r9, r7, ip, lsl #1 @ Fout4.i = s5.i + s6.i + ldr r10, [sp, #28] @ s11.i + str r8, [r1], #4 + str r9, [r4], #4 + add r8, r10, lr, lsl #1 @ Fout2.i = s11.i + s12.i + sub r9, r10, lr, lsl #1 @ Fout3.i + str r8, [r2], #4 + str r9, [r3], #4 + + ldr r7, [sp, #32] @ fstride, bytes + ldr ip, [sp, #36] @ limit + add r6, r6, r7 @ displacement for the next pass + cmp r6, ip + bne .Lb5_pass + + ldr r7, [sp, #44] @ N left + subs r7, r7, #1 + str r7, [sp, #44] + bne .Lb5_outer + add sp, sp, #56 + pop {r4-r11, pc} +.Lb5_yc: + .word 18318 +.Lb5_yab: + .word 0xb4c38644 + .size kf_bfly5_armv5e, .-kf_bfly5_armv5e diff --git a/lib/rbcodec/codecs/libopus/celt/arm/mdct_armv4.h b/lib/rbcodec/codecs/libopus/celt/arm/mdct_armv4.h new file mode 100644 index 0000000000..1088d5eac1 --- /dev/null +++ b/lib/rbcodec/codecs/libopus/celt/arm/mdct_armv4.h @@ -0,0 +1,75 @@ +/* Copyright (c) 2025 Xiph.Org Foundation and contributors + Copyright (c) 2026 Michael Giacomelli + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef MDCT_ARMv4_H +#define MDCT_ARMv4_H + +/* Hand-written inner loops for the backward MDCT. Only the three loops are + replaced; the setup around them stays in C, so mdct.c remains readable and + the assembly needs no knowledge of mdct_lookup. See + celt/arm/mdct_armv4_asm.S for why the compiled loops lose. + + Building with OPUS_ARM_NO_MDCT_ASM selects the C loops instead, which is + how the two are compared. */ + +#if defined(OPUS_ARM_ASM_ARMV4_ONLY) && defined(FIXED_POINT) \ + && !defined(OPUS_ARM_NO_MDCT_ASM) + +#define OVERRIDE_MDCT_PREROT +#define OVERRIDE_MDCT_POSTROT +#define OVERRIDE_MDCT_MIRROR + +#define mdct_prerot_opt mdct_prerot_armv4 +#define mdct_postrot_opt mdct_postrot_armv4 + +#ifdef OPUS_PFA +#define OVERRIDE_MDCT_POSTROT_PFA +#define mdct_postrot_pfa_opt mdct_postrot_pfa_armv4 +#endif +#define mdct_mirror_opt mdct_mirror_armv4 + +/* step is a byte stride, so the caller scales by sizeof(kiss_fft_scalar). */ +void mdct_prerot_armv4(const kiss_fft_scalar *xp1, + const kiss_fft_scalar *xp2, + const kiss_twiddle_scalar *t, + const opus_int16 *bitrev, + kiss_fft_scalar *yp, int N4, int step); + +void mdct_postrot_armv4(kiss_fft_scalar *yp0, kiss_fft_scalar *yp1, + const kiss_twiddle_scalar *t, int N4, int count); + +#ifdef OPUS_PFA +void mdct_postrot_pfa_armv4(const kiss_fft_scalar *S, kiss_fft_scalar *yp0, + kiss_fft_scalar *yp1, const kiss_twiddle_scalar *t, + const opus_int16 *pmap, int N4); +#endif + +void mdct_mirror_armv4(kiss_fft_scalar *xp1, kiss_fft_scalar *yp1, + const opus_val16 *wp1, const opus_val16 *wp2, + int count); + +#endif + +#endif /* MDCT_ARMv4_H */ diff --git a/lib/rbcodec/codecs/libopus/celt/arm/mdct_armv4_asm.S b/lib/rbcodec/codecs/libopus/celt/arm/mdct_armv4_asm.S new file mode 100644 index 0000000000..6115cb5ec3 --- /dev/null +++ b/lib/rbcodec/codecs/libopus/celt/arm/mdct_armv4_asm.S @@ -0,0 +1,305 @@ +/* ARMv4 inner loops for the CELT backward MDCT. + * + * Copyright (c) 2007-2008 CSIRO + * Copyright (c) 2007-2008 Xiph.Org Foundation + * Copyright (c) 2008 Gregory Maxwell + * Copyright (c) 2026 Michael Giacomelli + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the conditions stated in + * celt/mdct.c are met. + * + * Why this exists + * --------------- + * The three loops here are the whole cost of clt_mdct_backward, and what the + * compiled versions lose is registers. Each loop needs more live values + * than gcc can hold, so it spills the loop-invariant pointers, strides and + * limits and reloads them every pass: five stack accesses per iteration in + * the post-rotation alone. Here the bookkeeping stays resident, and the + * only reload is the pre-rotation's input stride, which buys the seventh + * working register the complex multiply needs. + * + * Holding the twiddle as a 16-bit value and accumulating the product pair + * with smull/smlal is what makes that fit: seven live registers, where the + * shifted MULT16_32_Q15 form needs nine. The ARM7TDMI datasheet also + * promises a 16-bit Rs will finish in two multiplier passes rather than + * four, but device measurement shows no sign of that paying here. Treat + * the form as register economy, not as a multiplier win. + * + * ldm/stm helps only where the addressing allows it. The post-rotation + * reads and writes contiguous pairs, so it bursts. The pre-rotation reads + * the spectrum through a runtime stride and writes through the bit-reversal + * table, so only its 8-byte output pair merges, and the TDAC mirror walks + * two pointers in opposite directions and merges nothing. + * + * All three keep all 32 bits of each Q15 product, where MULT16_32_Q15_armv4 + * drops the low bit for speed. The backward MDCT applies three such rounds + * per sample, so this is worth about 9.5 dB rather than being incidental. + */ + +#if defined(__thumb__) || defined(__thumb2__) +#error "mdct_armv4_asm.S must be assembled in ARM mode" +#endif + +/* These kernels are 2,892 bytes that carry 43% of the cycles an ARMv4 decode + * spends, which makes them the densest thing in the codec to put in IRAM. + * config.h sets OPUS_ARM_ICODE on the targets whose codec IRAM window has + * room; everywhere else this stays in .text. The firmware config.h it reads + * that from is assembly-safe, and libopus/config.h guards the rest of its + * includes with __ASSEMBLER__ so this include costs nothing here. */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef OPUS_ARM_ICODE + .section .icode, "ax", %progbits +#else + .text +#endif + .align 2 + +/* One Q15 complex multiply, the same register discipline as the C_MUL asm in + arm/kiss_fft_armv4.h. a is (\ar, \ai), b is (\br, \bi), \tt is scratch and + the 64-bit low word. Results: \mr = a.r*b.r - a.i*b.i, \mi = a.r*b.i + + a.i*b.r. \bi and \br are destroyed. \mr must differ from \ar and \br, + \mi from \ai and \tt. \tt and \mr may be the same register: \tt is dead + after the fourth instruction and \mr is not written until the sixth, and + sharing them is what lets these loops keep their invariants resident. */ + .macro CMULQ15 ar, ai, br, bi, tt, mr, mi + smull \tt, \mi, \ai, \br + smlal \tt, \mi, \ar, \bi + mov \tt, \tt, lsr #15 + orr \mi, \tt, \mi, lsl #17 + rsb \bi, \bi, #0 + smull \br, \mr, \ar, \br + smlal \br, \mr, \ai, \bi + mov \br, \br, lsr #15 + orr \mr, \br, \mr, lsl #17 + .endm + + +/* ------------------------------------------------------------------------ + * void mdct_prerot_armv4(const kiss_fft_scalar *xp1, + * const kiss_fft_scalar *xp2, + * const kiss_twiddle_scalar *t, + * const opus_int16 *bitrev, + * kiss_fft_scalar *yp, int N4, int step) + * + * yr = xp2*t[i] + xp1*t[N4+i]; yi = xp1*t[i] - xp2*t[N4+i] + * yp[2*rev+1] = yr; yp[2*rev] = yi; xp1 += step; xp2 -= step + * + * step is in bytes. Both halves of the output are one 8-byte pair, so they + * store together even though the address is scattered by the bitrev table. + * ------------------------------------------------------------------------ */ + .global mdct_prerot_armv4 + .type mdct_prerot_armv4, %function +mdct_prerot_armv4: + push {r4-r11, lr} + ldr r5, [sp, #36] @ yp + ldr r6, [sp, #40] @ N4, also the loop count + ldr lr, [sp, #44] @ step, in bytes + mov r4, r6, lsl #1 + sub r4, r4, #2 @ t[i] is read post-incremented, so the + @ offset on to t[N4+i] is two less + cmp r6, #0 + ble .Lpre_done +.Lpre_loop: + ldr r8, [r0], lr @ x1 = *xp1++ + ldr r9, [r1], -lr @ x2 = *xp2-- + ldrsh r10, [r2], #2 @ t[i] + ldrsh r11, [r2, r4] @ t[N4+i], and cover t[i]'s I cycle + CMULQ15 r8, r9, r10, r11, r7, r7, ip + ldrsh r10, [r3], #2 @ rev + subs r6, r6, #1 @ between the load and the address it + @ forms, where the counter is free + add r10, r5, r10, lsl #3 + stm r10, {r7, ip} @ yi at 2*rev, yr at 2*rev+1 + bne .Lpre_loop +.Lpre_done: + pop {r4-r11, pc} + .size mdct_prerot_armv4, .-mdct_prerot_armv4 + + +/* ------------------------------------------------------------------------ + * void mdct_postrot_armv4(kiss_fft_scalar *yp0, kiss_fft_scalar *yp1, + * const kiss_twiddle_scalar *t, int N4, int count) + * + * Walks the buffer from both ends at once, so each end contributes a + * contiguous complex pair: two ldm in, and the writes pair up as well, apart + * from yp0[0], which is stored early to free a register before the second + * complex multiply. + * + * The read of yp1 must happen before yp1[1] is written, which is why the + * store order here looks less symmetric than the arithmetic. + * ------------------------------------------------------------------------ */ + .align 2 + +/* Unreachable once the prime factor transform is built, so it gives its IRAM + back rather than sitting resident and unread. Still assembled: a size the + Good-Thomas path does not cover falls through to it, and correct-but-slow + from DRAM is the right failure mode. */ +#if defined(OPUS_ARM_ICODE) && !defined(OPUS_PFA) + .section .icode, "ax", %progbits +#else + .text +#endif + .align 2 + + .global mdct_postrot_armv4 + .type mdct_postrot_armv4, %function +mdct_postrot_armv4: + push {r4-r11, lr} + ldr r5, [sp, #36] @ count + mov r4, r3, lsl #1 @ byte offset from t[k] to t[N4+k] + sub r6, r3, #1 + add r3, r2, r6, lsl #1 @ tb = &t[N4-1], walks down + cmp r5, #0 + ble .Lpost_done +.Lpost_loop: + ldrsh r7, [r2, r4] @ t[N4+i] + ldrsh r6, [r2], #2 @ t[i] + ldm r0, {r8, r9} @ im = yp0[0], re = yp0[1] + /* yr = re*t0 + im*t1, yi = re*t1 - im*t0 */ + CMULQ15 r9, r8, r7, r6, lr, r11, ip + ldm r1, {r8, r9} @ im = yp1[0], re = yp1[1] + str ip, [r0] @ yp0[0] = yr + ldrsh r7, [r3, r4] @ t[N2-i-1] + ldrsh r6, [r3], #-2 @ t[N4-i-1] + /* mr is yi' and mi is yr', so the pair lands with yr' in the lower + register and stores straight into yp1. */ + CMULQ15 r9, r8, r7, r6, lr, ip, r10 + stm r1, {r10, r11} @ yp1[0] = yr', yp1[1] = yi + str ip, [r0, #4] @ yp0[1] = yi' + add r0, r0, #8 + sub r1, r1, #8 + subs r5, r5, #1 + bne .Lpost_loop +.Lpost_done: + pop {r4-r11, pc} + .size mdct_postrot_armv4, .-mdct_postrot_armv4 + + +/* ------------------------------------------------------------------------ + * void mdct_mirror_armv4(kiss_fft_scalar *xp1, kiss_fft_scalar *yp1, + * const opus_val16 *wp1, const opus_val16 *wp2, + * int count) + * + * The TDAC fold. Two data pointers walk in opposite directions and two + * window pointers do the same, so nothing here is contiguous and nothing + * bursts; the win is entirely the multiplier operand form. + * ------------------------------------------------------------------------ */ + .align 2 + +#ifdef OPUS_ARM_ICODE + .section .icode, "ax", %progbits +#else + .text +#endif + .align 2 + + .global mdct_mirror_armv4 + .type mdct_mirror_armv4, %function +mdct_mirror_armv4: + push {r4-r11, lr} + ldr r4, [sp, #36] @ count + cmp r4, #0 + ble .Lmir_done +.Lmir_loop: + ldrsh r5, [r3], #-2 @ w2 = *wp2-- + ldrsh r6, [r2], #2 @ w1 = *wp1++ + ldr r7, [r0] @ x1 = *xp1 + ldr r8, [r1] @ x2 = *yp1 + /* lo = w2*x2 - w1*x1, hi = w1*x2 + w2*x1 */ + CMULQ15 r8, r7, r5, r6, lr, r9, ip + str r9, [r1], #4 @ *yp1++ = lo + str ip, [r0], #-4 @ *xp1-- = hi + subs r4, r4, #1 + bne .Lmir_loop +.Lmir_done: + pop {r4-r11, pc} + .size mdct_mirror_armv4, .-mdct_mirror_armv4 + + +#ifdef OPUS_PFA +#ifdef OPUS_ARM_ICODE + .section .icode, "ax", %progbits +#else + .text +#endif + +/* ------------------------------------------------------------------------ + * void mdct_postrot_pfa_armv4(const kiss_fft_scalar *S, + * kiss_fft_scalar *yp0, kiss_fft_scalar *yp1, + * const kiss_twiddle_scalar *t, + * const opus_int16 *pmap, int N4) + * + * The post-rotation for the Good-Thomas transform. Same arithmetic as + * mdct_postrot_armv4, but the spectrum arrives permuted -- X[k] sits at + * M*(k mod 15) + (k mod M) -- so each end is gathered through pmap, which + * holds that index already scaled to a byte offset. One table serves both + * ends: the ascending half reads it forwards, the descending half backwards. + * + * Gathering is why this reads a different buffer than it writes, and that + * turns out to simplify the kernel rather than complicate it. The + * mixed-radix version has to store yp0[0] early and defer yp1[1] because it + * must read yp1 before overwriting it; here yp0 and yp1 are write-only, so + * both halves of each complex multiply store as soon as they exist and + * nothing stays live across the second one. + * + * No iteration counter: the two pmap pointers walk towards each other and + * the loop ends when they cross, which is the same (N4+1)>>1 iterations for + * the even N4 every CELT size has. + * + * N4*2, the offset from t[i] to t[N4+i], does fit in a register after all: + * the complex multiply's 64-bit scratch and its real result can share one, + * so the gather address register is free by the time the multiply needs a + * temporary. It used to be reloaded from the frame once per end, which cost + * six cycles an iteration for want of that observation. + * ------------------------------------------------------------------------ */ + .align 2 + .global mdct_postrot_pfa_armv4 + .type mdct_postrot_pfa_armv4, %function +mdct_postrot_pfa_armv4: + push {r4-r11, lr} + ldr r5, [sp, #36] @ pmap + ldr r6, [sp, #40] @ N4 + mov r7, r6, lsl #1 @ byte offset from t[k] to t[N4+k] + sub r6, r6, #1 + add r4, r3, r6, lsl #1 @ tb = &t[N4-1], walks down + add r6, r5, r6, lsl #1 @ pd = &pmap[N4-1], walks down + cmp r5, r6 + bhi .Lpost_pfa_done +.Lpost_pfa_loop: + /* ascending end: X[i]. The gather index leads, so the address add and + the ldm each have an unrelated load in front of them, and the pair + lands a cycle before the multiply that wants it. */ + ldrsh r12, [r5], #2 @ byte offset of X[i] + ldrsh r11, [r3, r7] @ t1 = t[N4+i] + add r12, r0, r12 + ldm r12, {r8, r9} @ im = S[2p], re = S[2p+1] + ldrsh r10, [r3], #2 @ t0 = t[i] + CMULQ15 r9, r8, r11, r10, r12, r12, lr + str lr, [r1] @ yp0[0] = yr + str r12, [r2, #4] @ yp1[1] = yi + + /* descending end: X[N4-1-i] */ + ldrsh r12, [r6], #-2 @ byte offset of X[N4-1-i] + ldrsh r11, [r4, r7] @ t1 = t[N2-i-1] + add r12, r0, r12 + ldm r12, {r8, r9} + ldrsh r10, [r4], #-2 @ t0 = t[N4-i-1] + CMULQ15 r9, r8, r11, r10, r12, r12, lr + str lr, [r2] @ yp1[0] = yr' + str r12, [r1, #4] @ yp0[1] = yi' + + add r1, r1, #8 + sub r2, r2, #8 + cmp r5, r6 + bls .Lpost_pfa_loop @ bls, not blo, so an odd N4 still gets + @ its middle pair, as the C loop does +.Lpost_pfa_done: + pop {r4-r11, pc} + .size mdct_postrot_pfa_armv4, .-mdct_postrot_pfa_armv4 +#endif /* OPUS_PFA */ + + .section .note.GNU-stack,"",%progbits diff --git a/lib/rbcodec/codecs/libopus/celt/arm/mdct_armv5e.h b/lib/rbcodec/codecs/libopus/celt/arm/mdct_armv5e.h new file mode 100644 index 0000000000..e6b9e6c811 --- /dev/null +++ b/lib/rbcodec/codecs/libopus/celt/arm/mdct_armv5e.h @@ -0,0 +1,76 @@ +/* Copyright (c) 2025 Xiph.Org Foundation and contributors + Copyright (c) 2026 Michael Giacomelli + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef MDCT_ARMv5E_H +#define MDCT_ARMv5E_H + +/* Hand-written inner loops for the backward MDCT, the ARMv5E counterpart of + arm/mdct_armv4.h. The compiled loops on this architecture already use the + 32x16 multiply; what they miss is the packed halfword operand, the + multiply-accumulate and the multi-register transfer. See + celt/arm/mdct_armv5e_asm.S. + + These are bit-exact with the C on this architecture, unlike the ARMv4 + kernels, which are more accurate than theirs. + + Building with OPUS_ARM_NO_MDCT_ASM selects the C loops instead, which is + how the two are compared. */ + +#if defined(OPUS_ARM_ASM_ARMV5E_AND_LATER) && defined(FIXED_POINT) \ + && (ARM_ARCH >= 5) && !defined(OPUS_ARM_NO_MDCT_ASM) + +#define OVERRIDE_MDCT_PREROT +#define OVERRIDE_MDCT_POSTROT +#define OVERRIDE_MDCT_MIRROR + +#define mdct_prerot_opt mdct_prerot_armv5e +#define mdct_postrot_opt mdct_postrot_armv5e + +#ifdef OPUS_PFA +#define OVERRIDE_MDCT_POSTROT_PFA +#define mdct_postrot_pfa_opt mdct_postrot_pfa_armv5e +void mdct_postrot_pfa_armv5e(const kiss_fft_scalar *S, kiss_fft_scalar *yp0, + kiss_fft_scalar *yp1, const kiss_twiddle_scalar *t, + const opus_int16 *pmap, int N4); +#endif +#define mdct_mirror_opt mdct_mirror_armv5e + +/* step is a byte stride, so the caller scales by sizeof(kiss_fft_scalar). */ +void mdct_prerot_armv5e(const kiss_fft_scalar *xp1, + const kiss_fft_scalar *xp2, + const kiss_twiddle_scalar *t, + const opus_int16 *bitrev, + kiss_fft_scalar *yp, int N4, int step); + +void mdct_postrot_armv5e(kiss_fft_scalar *yp0, kiss_fft_scalar *yp1, + const kiss_twiddle_scalar *t, int N4, int count); + +void mdct_mirror_armv5e(kiss_fft_scalar *xp1, kiss_fft_scalar *yp1, + const opus_val16 *wp1, const opus_val16 *wp2, + int count); + +#endif + +#endif /* MDCT_ARMv5E_H */ diff --git a/lib/rbcodec/codecs/libopus/celt/arm/mdct_armv5e_asm.S b/lib/rbcodec/codecs/libopus/celt/arm/mdct_armv5e_asm.S new file mode 100644 index 0000000000..c161e4d13c --- /dev/null +++ b/lib/rbcodec/codecs/libopus/celt/arm/mdct_armv5e_asm.S @@ -0,0 +1,469 @@ +/* ARMv5E inner loops for the CELT backward MDCT. + * + * Copyright (c) 2007-2008 CSIRO + * Copyright (c) 2007-2008 Xiph.Org Foundation + * Copyright (c) 2008 Gregory Maxwell + * Copyright (c) 2026 Michael Giacomelli + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the conditions stated in + * celt/mdct.c are met. + * + * Why this exists + * --------------- + * On ARMv5E the compiled rotations already get the 32x16 multiply, because + * MULT16_32_Q15 resolves to smulwb in arm/fixed_armv5e.h. What they never + * get is the rest of the instruction set: mdct.o from a Sansa Clip+ build + * contains 34 smulwb and not one smulwt, smlaw, ldrd or multi-register + * transfer. Three things follow. + * + * The twiddles are loaded one halfword at a time. A kiss_twiddle_scalar is + * 16 bits, so t[i] and t[i+1] are one aligned word; unrolling by two lets a + * single ldr fetch both and smulwb/smulwt pick between them. That halves + * the twiddle loads, and an ldr also resolves a cycle sooner than an ldrsh. + * + * Each product pair is narrowed twice. S_MUL(a,t0)+S_MUL(b,t1) compiles to + * two smulwb, two shifts and an add, but smulwb followed by smlawb leaves + * the sum at Q16 and needs one shift. This is bit-exact with the C: both + * products are still truncated separately inside their own instruction, and + * the shift distributes over the sum. + * + * The subtraction is deliberately not fused. smlaw only adds, so fusing it + * would mean negating a twiddle, and truncation is not symmetric about + * zero: trunc(a)+trunc(-b) carries a whole unit of bias where + * trunc(a)-trunc(b) carries none. It would also cost an rsb per twiddle, + * which the packed form cannot supply. Two smulw and a shifted subtract + * stay. + * + * Accuracy is therefore identical to the C path on this architecture, not + * better as on ARMv4. The exact smull/smlal narrowing the ARMv4 kernels + * use is worth about 9.5 dB here too, but it costs a register pair and + * three issue cycles against one, which is a different trade on this core. + * + * Word loads need a word-aligned table and an even N4. The static CELT + * modes give both, but neither is guaranteed by the API, so each kernel + * tests its pointers and falls through to a scalar loop that doubles as the + * odd-count tail. + */ + +#if defined(__thumb__) || defined(__thumb2__) +#error "mdct_armv5e_asm.S must be assembled in ARM mode" +#endif + +/* config.h decides whether the prime factor transform is built, and the + post-rotation kernel below only exists when it is. libopus/config.h + guards its own includes with __ASSEMBLER__, so this costs nothing here. */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + + .text + .align 2 + +/* The scalar paths below are the odd-count tail and the fallback for a table + the unroll cannot address as words. Neither runs on the static CELT modes, + so build with -DMDCT_ARMV5E_FORCE_SCALAR to route everything through them + and confirm they still agree with the C. */ + .macro FORCE_SCALAR +#ifdef MDCT_ARMV5E_FORCE_SCALAR + orr ip, ip, #1 +#endif + .endm + + +/* ------------------------------------------------------------------------ + * void mdct_prerot_armv5e(const kiss_fft_scalar *xp1, + * const kiss_fft_scalar *xp2, + * const kiss_twiddle_scalar *t, + * const opus_int16 *bitrev, + * kiss_fft_scalar *yp, int N4, int step) + * + * yr = xp2*t[i] + xp1*t[N4+i]; yi = xp1*t[i] - xp2*t[N4+i] + * yp[2*rev+1] = yr; yp[2*rev] = yi; xp1 += step; xp2 -= step + * + * step is in bytes. The output pair is 8 contiguous bytes even though the + * bitrev table scatters where it lands, so it stores as one stm. + * + * r10 holds t[i] packed with t[i+1] and r11 holds t[N4+i] with t[N4+i+1]; + * \sel picks the half. The scalar path loads with ldrsh and passes b, + * which reads the same bits. + * ------------------------------------------------------------------------ */ + .macro PREROT_ONE sel + ldr r5, [r1], -r6 @ x2 = *xp2, xp2 -= step + ldr r9, [r0], r6 @ x1 = *xp1, xp1 += step, and x2's + @ interlock is covered by it + smulw\sel lr, r5, r10 @ x2 * t[i] + smlaw\sel lr, r9, r11, lr @ + x1 * t[N4+i] -> yr at Q16 + smulw\sel ip, r9, r10 @ x1 * t[i] + smulw\sel r9, r5, r11 @ x2 * t[N4+i] + ldrsh r5, [r3], #2 @ rev = *bitrev++ + sub ip, ip, r9 + mov lr, lr, lsl #1 @ yr + mov ip, ip, lsl #1 @ yi + add r5, r4, r5, lsl #3 + stm r5, {ip, lr} @ yp[2*rev] = yi, yp[2*rev+1] = yr + .endm + + .global mdct_prerot_armv5e + .type mdct_prerot_armv5e, %function +mdct_prerot_armv5e: + push {r4-r11, lr} + sub sp, sp, #4 + ldr r4, [sp, #40] @ yp + ldr r5, [sp, #44] @ N4 + ldr r6, [sp, #48] @ step, in bytes + mov r7, r5, lsl #1 @ byte offset from t[i] to t[N4+i] + and r9, r5, #1 @ odd sample left over by the unroll + mov r8, r5, lsr #1 @ pairs + orr ip, r2, r7 @ word-addressable table and stride? + FORCE_SCALAR + tst ip, #3 + movne r8, #0 @ no: run the whole thing scalar + movne r9, r5 + str r9, [sp] @ scalar count, reloaded once + cmp r8, #0 + beq .Lpre5_scalar +.Lpre5_pairs: + ldr r11, [r2, r7] @ t[N4+i] : t[N4+i+1] + ldr r10, [r2], #4 @ t[i] : t[i+1] + PREROT_ONE b + PREROT_ONE t + subs r8, r8, #1 + bne .Lpre5_pairs +.Lpre5_scalar: + ldr r8, [sp] + cmp r8, #0 + beq .Lpre5_done +.Lpre5_sloop: + ldrsh r11, [r2, r7] @ t[N4+i] + ldrsh r10, [r2], #2 @ t[i] + PREROT_ONE b + subs r8, r8, #1 + bne .Lpre5_sloop +.Lpre5_done: + add sp, sp, #4 + pop {r4-r11, pc} + .size mdct_prerot_armv5e, .-mdct_prerot_armv5e + + +/* ------------------------------------------------------------------------ + * void mdct_postrot_armv5e(kiss_fft_scalar *yp0, kiss_fft_scalar *yp1, + * const kiss_twiddle_scalar *t, int N4, int count) + * + * Walks the buffer from both ends at once, so each end reads a contiguous + * complex pair with one ldm. Four twiddle streams run at once: t[i] and + * t[N4+i] climb, t[N4-i-1] and t[N2-i-1] descend. Both directions pack, + * the descending ones in reverse, so the unrolled pair reads them t first + * and b second. + * + * Every read happens before every write, which is what makes the doubled + * middle pair produced by an odd N4 come out the same as the C. + * ------------------------------------------------------------------------ */ + .macro POSTROT_ONE sel, dsel + ldm r0, {r8, r9} @ im = yp0[0], re = yp0[1] + smulw\sel lr, r9, r10 @ re * t0 + smlaw\sel lr, r8, r11, lr @ + im * t1 -> yr at Q16 + smulw\sel ip, r9, r11 @ re * t1 + smulw\sel r8, r8, r10 @ im * t0 + mov lr, lr, lsl #1 @ yr + sub ip, ip, r8 + mov ip, ip, lsl #1 @ yi + ldm r1, {r8, r9} @ im = yp1[0], re = yp1[1] + str lr, [r0] @ yp0[0] = yr + smulw\dsel lr, r9, r5 @ re * t0b + smlaw\dsel lr, r8, r6, lr @ + im * t1b -> yr2 at Q16 + smulw\dsel r9, r9, r6 @ re * t1b + smulw\dsel r8, r8, r5 @ im * t0b + mov lr, lr, lsl #1 @ yr2 + sub r9, r9, r8 + str lr, [r1] @ yp1[0] = yr2 + str ip, [r1, #4] @ yp1[1] = yi + mov r9, r9, lsl #1 @ yi2 + str r9, [r0, #4] @ yp0[1] = yi2 + add r0, r0, #8 + sub r1, r1, #8 + .endm + + .align 2 + .global mdct_postrot_armv5e + .type mdct_postrot_armv5e, %function +mdct_postrot_armv5e: + push {r4-r11, lr} + sub sp, sp, #4 + ldr r4, [sp, #40] @ count; r4 counts, r8 and r9 are scratch + mov r7, r3, lsl #1 @ byte offset from t[k] to t[N4+k] + sub r8, r3, #1 + add r3, r2, r8, lsl #1 @ descending pointer, at t[N4-i-1] + and r9, r4, #1 @ odd iteration left over + eor ip, r3, #2 @ its word sits two bytes below + orr ip, ip, r2 + orr ip, ip, r7 + FORCE_SCALAR + tst ip, #3 + movne r9, r4 @ not word-addressable: go scalar + mov r4, r4, lsr #1 @ pairs + movne r4, #0 + str r9, [sp] + cmp r4, #0 + beq .Lpost5_scalar +.Lpost5_pairs: + ldr r10, [r2] @ t[i] : t[i+1] + ldr r11, [r2, r7] @ t[N4+i] : t[N4+i+1] + add lr, r3, r7 + ldr r5, [r3, #-2] @ t[N4-i-2] : t[N4-i-1] + ldr r6, [lr, #-2] @ t[N2-i-2] : t[N2-i-1] + add r2, r2, #4 + sub r3, r3, #4 + POSTROT_ONE b, t + POSTROT_ONE t, b + subs r4, r4, #1 + bne .Lpost5_pairs +.Lpost5_scalar: + ldr r4, [sp] + cmp r4, #0 + beq .Lpost5_done +.Lpost5_sloop: + ldrsh r11, [r2, r7] @ t[N4+i] + ldrsh r10, [r2], #2 @ t[i] + ldrsh r6, [r3, r7] @ t[N2-i-1] + ldrsh r5, [r3], #-2 @ t[N4-i-1] + POSTROT_ONE b, b + subs r4, r4, #1 + bne .Lpost5_sloop +.Lpost5_done: + add sp, sp, #4 + pop {r4-r11, pc} + .size mdct_postrot_armv5e, .-mdct_postrot_armv5e + + +/* ------------------------------------------------------------------------ + * void mdct_mirror_armv5e(kiss_fft_scalar *xp1, kiss_fft_scalar *yp1, + * const opus_val16 *wp1, const opus_val16 *wp2, + * int count) + * + * The TDAC fold. The two data pointers walk in opposite directions and + * never meet, so nothing there bursts, but the two window pointers pack + * exactly as the twiddles do. + * ------------------------------------------------------------------------ */ + .macro MIRROR_ONE sel, dsel + ldr r9, [r0] @ x1 = *xp1 + ldr ip, [r1] @ x2 = *yp1 + smulw\dsel lr, ip, r6 @ x2 * w2 + smulw\sel r8, r9, r5 @ x1 * w1 + sub lr, lr, r8 + mov lr, lr, lsl #1 + str lr, [r1], #4 @ *yp1++ = w2*x2 - w1*x1 + smulw\sel lr, ip, r5 @ x2 * w1 + smlaw\dsel lr, r9, r6, lr @ + x1 * w2 + mov lr, lr, lsl #1 + str lr, [r0], #-4 @ *xp1-- = w1*x2 + w2*x1 + .endm + + .align 2 + .global mdct_mirror_armv5e + .type mdct_mirror_armv5e, %function +mdct_mirror_armv5e: + push {r4-r11, lr} + sub sp, sp, #4 + ldr r4, [sp, #40] @ count; r4 counts, r8 and r9 are scratch + and r9, r4, #1 + eor ip, r3, #2 + orr ip, ip, r2 + FORCE_SCALAR + tst ip, #3 + movne r9, r4 + mov r4, r4, lsr #1 + movne r4, #0 + str r9, [sp] + cmp r4, #0 + beq .Lmir5_scalar +.Lmir5_pairs: + ldr r5, [r2], #4 @ w1[i] : w1[i+1] + ldr r6, [r3, #-2] @ w2[-i-1] : w2[-i] + sub r3, r3, #4 + MIRROR_ONE b, t + MIRROR_ONE t, b + subs r4, r4, #1 + bne .Lmir5_pairs +.Lmir5_scalar: + ldr r4, [sp] + cmp r4, #0 + beq .Lmir5_done +.Lmir5_sloop: + ldrsh r5, [r2], #2 @ w1 + ldrsh r6, [r3], #-2 @ w2 + MIRROR_ONE b, b + subs r4, r4, #1 + bne .Lmir5_sloop +.Lmir5_done: + add sp, sp, #4 + pop {r4-r11, pc} + .size mdct_mirror_armv5e, .-mdct_mirror_armv5e + +#ifdef OPUS_PFA + .text + .align 2 + + + +/* One post-rotated output. + * + * \sel picks which half of the packed twiddle pair to use + * \pm the pmap pointer, post-incremented by \pi + * \b0/\o0 where yr goes, \b1/\o1 where yi goes + * + * r10 holds t[k] packed with its neighbour and r11 holds t[N4+k] with its + * neighbour, so two loads serve two outputs. The gather is an ldrsh of the + * byte offset and one ldm, which fetches both halves of the complex value in + * a single transaction. + */ +/* The gather index arrives in r12, and this body loads the next one before + it needs its own loaded pair -- an index is a halfword, which interlocks + for two cycles if the address add follows it, and the ldm interlocks for + one if the multiply does. Rotating the load one body ahead covers both, + and it is the only place to put it: every other register is live. + + That in turn is why yr stores before yi is computed. Holding the index + in r12 costs the body its second scratch, so the accumulator has to be + free again before the imaginary half starts. + + \npm and \npi name the pointer the NEXT body reads, or none at the end of + the chain, where the loop head reloads instead. */ + .macro PR5_PFA sel, npm, npi, b0, o0, b1, o1 + add r12, r0, r12 + ldm r12, {r9, lr} @ r9 = im = S[2p], lr = re = S[2p+1]; + @ ldm fills in register order, not the + @ order written, so r9 takes the lower + @ address whichever way the list reads + .ifnc \npm, none + ldrsh r12, [\npm], #\npi @ the next body's index + .endif + smulw\sel r8, lr, r10 @ re * t0 + smlaw\sel r8, r9, r11, r8 @ + im * t1 -> yr at Q16 + mov r8, r8, lsl #1 + str r8, [\b0, #\o0] + smulw\sel r8, lr, r11 @ re * t1 + smulw\sel lr, r9, r10 @ im * t0 + sub r8, r8, lr + mov r8, r8, lsl #1 + str r8, [\b1, #\o1] + .endm + +/* The same body for the scalar fallback, which loads its own index: it runs + one output per end, so there is no next body to rotate against. */ + .macro PR5_PFA_S sel, pm, pi, b0, o0, b1, o1 + ldrsh r12, [\pm], #\pi + add r12, r0, r12 + ldm r12, {r9, lr} + smulw\sel r8, lr, r10 + smlaw\sel r8, r9, r11, r8 + smulw\sel r12, lr, r11 + smulw\sel lr, r9, r10 + mov r8, r8, lsl #1 + sub r12, r12, lr + str r8, [\b0, #\o0] + mov r12, r12, lsl #1 + str r12, [\b1, #\o1] + .endm + +/* ------------------------------------------------------------------------ + * void mdct_postrot_pfa_armv5e(const kiss_fft_scalar *S, + * kiss_fft_scalar *yp0, kiss_fft_scalar *yp1, + * const kiss_twiddle_scalar *t, + * const opus_int16 *pmap, int N4) + * + * The post-rotation for the Good-Thomas transform. Same arithmetic as + * mdct_postrot_armv5e; what changes is that the spectrum arrives permuted, + * X[k] at M*(k mod 15) + (k mod M), so each end is gathered through pmap, + * which already holds that index as a byte offset. The ascending half reads + * the table forwards and the descending half backwards. + * + * Gathering makes yp0 and yp1 write-only, and that is what lets the twiddles + * stay packed. A kiss_twiddle_scalar is 16 bits, so t[k] and t[k+1] are one + * aligned word; the mixed-radix kernel exploits that by holding all four + * twiddle words -- both ends at once -- across a body that does two outputs + * from each. Here two of those registers are spent on the pmap pointers, so + * instead the body does both ascending outputs, reloads the pair for the + * descending end, and does both of those. That reordering is legal only + * because nothing is read back: in the in-place version the descending + * output has to be read before the ascending one overwrites it, which forces + * the two ends to interleave. + * + * No iteration counter either: the two pmap pointers walk towards each other + * and the loop ends when they cross. Four outputs an iteration and N4 a + * multiple of four -- 60, 120, 240, 480 -- means that lands exactly. + * + * The scalar path below is the fallback for a trig table the packed loads + * cannot address as words, or an N4 the unroll does not divide. No CELT + * mode reaches it; build with -DMDCT_ARMV5E_FORCE_SCALAR to route everything + * through it and confirm it still agrees. + * ------------------------------------------------------------------------ */ + .align 2 + .global mdct_postrot_pfa_armv5e + .type mdct_postrot_pfa_armv5e, %function +mdct_postrot_pfa_armv5e: + push {r4-r11, lr} + ldr r5, [sp, #36] @ pmap + ldr r12, [sp, #40] @ N4 + mov r7, r12, lsl #1 @ byte offset from t[k] to t[N4+k] + sub r8, r12, #1 + add r4, r3, r8, lsl #1 @ tb = &t[N4-1], walks down + add r6, r5, r8, lsl #1 @ pd = &pmap[N4-1], walks down + + /* The packed path needs the trig table word-addressable and N4 a + multiple of four; the second gives the first its stride and lets the + crossing test land exactly. */ + orr r8, r3, r12 +#ifdef MDCT_ARMV5E_FORCE_SCALAR + orr r8, r8, #1 +#endif + tst r8, #3 + bne .Lpost5_pfa_scalar + + cmp r5, r6 + bhi .Lpost5_pfa_done +.Lpost5_pfa_pairs: + ldrsh r12, [r5], #2 @ index of X[i]; the twiddle loads below + @ stand between it and the address add + ldr r10, [r3] @ t[i] : t[i+1] + ldr r11, [r3, r7] @ t[N4+i] : t[N4+i+1] + add r3, r3, #4 + PR5_PFA b, r5, 2, r1, 0, r2, 4 @ X[i] + PR5_PFA t, r6, -2, r1, 8, r2, -4 @ X[i+1] + + add lr, r4, r7 + ldr r10, [r4, #-2] @ t[N4-i-2] : t[N4-i-1] + ldr r11, [lr, #-2] @ t[N2-i-2] : t[N2-i-1] + sub r4, r4, #4 + PR5_PFA t, r6, -2, r2, 0, r1, 4 @ X[N4-1-i] + PR5_PFA b, none, 0, r2, -8, r1, 12 @ X[N4-2-i] + + add r1, r1, #16 + sub r2, r2, #16 + cmp r5, r6 + blo .Lpost5_pfa_pairs + b .Lpost5_pfa_done + +/* One output per end per iteration, twiddles a halfword at a time. The + crossing test is bls rather than blo so an odd N4 still computes its + middle pair, which is what the C loop to (N4+1)>>1 does. */ +.Lpost5_pfa_scalar: + cmp r5, r6 + bhi .Lpost5_pfa_done +.Lpost5_pfa_sloop: + ldrsh r11, [r3, r7] @ t[N4+i] + ldrsh r10, [r3], #2 @ t[i] + PR5_PFA_S b, r5, 2, r1, 0, r2, 4 + ldrsh r11, [r4, r7] @ t[N2-i-1] + ldrsh r10, [r4], #-2 @ t[N4-i-1] + PR5_PFA_S b, r6, -2, r2, 0, r1, 4 + add r1, r1, #8 + sub r2, r2, #8 + cmp r5, r6 + bls .Lpost5_pfa_sloop +.Lpost5_pfa_done: + pop {r4-r11, pc} + .size mdct_postrot_pfa_armv5e, .-mdct_postrot_pfa_armv5e +#endif + + .section .note.GNU-stack,"",%progbits diff --git a/lib/rbcodec/codecs/libopus/celt/arm/normres_armv5e_asm.S b/lib/rbcodec/codecs/libopus/celt/arm/normres_armv5e_asm.S new file mode 100644 index 0000000000..b1884eac6a --- /dev/null +++ b/lib/rbcodec/codecs/libopus/celt/arm/normres_armv5e_asm.S @@ -0,0 +1,81 @@ +/* ARMv5E inner loop for normalise_residual. + * + * Copyright (c) 2007-2008 CSIRO + * Copyright (c) 2007-2009 Xiph.Org Foundation + * Copyright (c) 2007-2009 Timothy B. Terriberry + * Copyright (c) 2026 Michael Giacomelli + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the conditions stated in + * celt/vq.c are met. + * + * Why this exists + * --------------- + * The compiled loop is eight instructions per element: an ldr handed + * straight to smulbb, which stalls a cycle, the rounding add, a + * register-specified shift at two cycles, a separate counter increment and + * compare, the strh, and a taken branch. + * + * Here the counter is the loop test, each load runs ahead of its use, and + * the loop unrolls by two so the taken branch happens once per pair. The + * register shift stays: the shift is set per call from the energy, so there + * is no fixed form to specialise to. + * + * Bit-exact. iy is int, and smulbb reads exactly the low halfword that + * MULT16_16 casts it to. + */ + +#if defined(__thumb__) || defined(__thumb2__) +#error "normres_armv5e_asm.S must be assembled in ARM mode" +#endif + + .text + .align 2 + +/* ------------------------------------------------------------------------ + * void normres_scale_armv5e(celt_norm *X, const int *iy, int N, int g, + * int shift) + * + * X[i] = (g*iy[i] + (1<>1)) >> shift for i in [0,N), 1 <= shift <= 31. + * N is at least 1, as in the do-while it replaces; a non-positive N writes + * nothing. + * + * r0 X, r1 iy, r2 count, r3 g, r4 rounding term, ip shift, r5 and lr the + * two elements of a pair. + * ------------------------------------------------------------------------ */ + .global normres_scale_armv5e + .type normres_scale_armv5e, %function +normres_scale_armv5e: + push {r4, r5, lr} + ldr ip, [sp, #12] @ shift + mov r4, #1 + mov r4, r4, lsl ip + mov r4, r4, lsr #1 @ 1<>1 + subs r2, r2, #2 @ r2 = elements left after the next pair + blt .Lnr_tail +.Lnr_pair: + ldr r5, [r1], #4 + ldr lr, [r1], #4 + subs r2, r2, #2 @ loop test, and the gap before use + smulbb r5, r3, r5 + smulbb lr, r3, lr + add r5, r5, r4 + add lr, lr, r4 + mov r5, r5, asr ip + mov lr, lr, asr ip + strh r5, [r0], #2 + strh lr, [r0], #2 + bge .Lnr_pair +.Lnr_tail: + adds r2, r2, #2 @ 0 or 1 left, or negative for N <= 0 + ble .Lnr_done + ldr r5, [r1], #4 + smulbb r5, r3, r5 + add r5, r5, r4 + mov r5, r5, asr ip + strh r5, [r0], #2 +.Lnr_done: + pop {r4, r5, pc} + .size normres_scale_armv5e, .-normres_scale_armv5e + + .section .note.GNU-stack,"",%progbits diff --git a/lib/rbcodec/codecs/libopus/celt/arm/pfa_arm.h b/lib/rbcodec/codecs/libopus/celt/arm/pfa_arm.h new file mode 100644 index 0000000000..afb21561e1 --- /dev/null +++ b/lib/rbcodec/codecs/libopus/celt/arm/pfa_arm.h @@ -0,0 +1,30 @@ +/* ARM hooks for the Good-Thomas 15-point kernel. + + Copyright (c) 2026 Michael Giacomelli + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the conditions stated in + celt/kiss_fft.c are met. +*/ +#ifndef PFA_ARM_H +#define PFA_ARM_H + +#if defined(FIXED_POINT) && defined(OPUS_PFA) + +#if defined(OPUS_ARM_ASM_ARMV5E_AND_LATER) + +#define OVERRIDE_PFA_FFT15 +void pfa_fft15_armv5e(const kiss_fft_cpx *in, kiss_fft_cpx *out, int ostride); +#define PFA_FFT15(in, out, ostride) pfa_fft15_armv5e(in, out, ostride) + +#elif defined(OPUS_ARM_ASM_ARMV4_ONLY) + +#define OVERRIDE_PFA_FFT15 +void pfa_fft15_armv4(const kiss_fft_cpx *in, kiss_fft_cpx *out, int ostride); +#define PFA_FFT15(in, out, ostride) pfa_fft15_armv4(in, out, ostride) + +#endif + +#endif /* FIXED_POINT && OPUS_PFA */ + +#endif /* PFA_ARM_H */ diff --git a/lib/rbcodec/codecs/libopus/celt/arm/pfa_armv4_asm.S b/lib/rbcodec/codecs/libopus/celt/arm/pfa_armv4_asm.S new file mode 100644 index 0000000000..7090cde931 --- /dev/null +++ b/lib/rbcodec/codecs/libopus/celt/arm/pfa_armv4_asm.S @@ -0,0 +1,290 @@ +/* ARMv4 Good-Thomas 15-point DFT for the CELT backward MDCT. + * + * Copyright (c) 2026 Michael Giacomelli + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the conditions stated in + * celt/kiss_fft.c are met. + * + * Why this exists + * --------------- + * 15 = 3*5 and the two are co-prime, so this is Good-Thomas inside + * Good-Thomas: five 3-point DFTs, then three 5-point DFTs, and nothing + * between them. A 15-point complex DFT for 40 real multiplies, where the + * mixed-radix chain it replaces spends 16 twiddle multiplies per point on + * the radix-5 pass alone. + * + * The arithmetic is the arithmetic the existing butterflies already use, so + * nothing new is being claimed about accuracy: epi3 is the radix-3 constant + * from kf_bfly3, and the radix-5 half keeps the sqrt(5)/4 collapse from + * kf_bfly5, where cos(2pi/5)+cos(4pi/5) is exactly -1/2 -- and the Q15 + * constants honour it exactly, 10126 - 26510 == -16384 -- so four cosine + * products become one multiply by yc. Q15 narrowing is the exact + * smull/shift/orr form the other ARMv4 kernels use, which keeps all 32 bits + * of the product where MULT16_32_Q15_armv4 drops the low one. + * + * Register pressure is the whole difficulty. A 5-point DFT needs eight live + * words across its eight multiplies plus two scratch, and ARM has fourteen + * registers to hold that, the output pointer, its stride and two constants. + * It fits only because the sum and difference pairs s5 and s11 are parked on + * the frame across the multiplies -- sixteen bytes, to the same cache line + * every call -- and because yc borrows the register ya.i will need later, + * reloading it afterwards for the price of one ldr per 5-point DFT. + * + * The output permutation costs nothing. Output k2 of the k1'th 5-point DFT + * belongs at 15-point index (10*k1 + 6*k2) mod 15; walking that from k1 in + * steps of 3 visits every one of the five, so the destinations are a single + * post-incremented pointer and the permutation is only a choice of which + * register to store. + */ + +#if defined(__thumb__) || defined(__thumb2__) +#error "pfa_armv4_asm.S must be assembled in ARM mode" +#endif + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef OPUS_ARM_ICODE + .section .icode, "ax", %progbits +#else + .text +#endif + .align 2 + +/* Frame: v[15] complex at 0..119, the s5/s11 park at 120..135, then the + saved output pointer, the 3-element output step and the 5-element stride + the three 5-point bases are derived from. */ +#define VOFF 0 +#define PARK 120 +#define SV_OUT 136 +#define SV_STEP3 140 +#define SV_FIVE 144 +#define FRAME 152 + +/* Q15 narrowing that keeps every bit of the product: see kiss_fft_armv4.h. */ + .macro QNARROW dst, lo, hi + mov \lo, \lo, lsr #15 + orr \dst, \lo, \hi, lsl #17 + .endm + +/* Advance the output pointer by d groups of three elements. The five + destinations of one 5-point DFT are (10*k1 + 6*k2) mod 15, which walking + k2 in the order 0,1,4,2,3 visits as steps of 3 with one wrap, so every + delta is a small multiple of r3 and none needs a multiply. */ + .macro PFA_ADV d + .if \d == 2 + add r0, r0, r3, lsl #1 + .elseif \d == 1 + add r0, r0, r3 + .elseif \d == -3 + sub r0, r0, r3, lsl #1 + sub r0, r0, r3 + .elseif \d == -4 + sub r0, r0, r3, lsl #2 + .else + .error "unexpected PFA store delta" + .endif + .endm + +/* One 5-point DFT over v[voff..voff+4], writing through r0. + * + * r0 = output, already at this DFT's base r1 = ya.i r2 = yb.i + * r3 = three output elements, in bytes + * + * Ten multiplies: two for the sqrt(5)/4 term that stands in for the four + * cosine products, eight for the sines. The four sine products of each + * half share their operands between s6 and s12, so both come out of one + * pass over s9 and s10 and neither has to be kept alive across the other. + */ + .macro PFA_DFT5 voff, d1, d2, d3, d4 + add r12, sp, #(\voff+8) + ldmia r12, {r4,r5,r6,r7,r8,r9} @ x1, x2, x3 + add r12, sp, #(\voff+32) + ldm r12, {r10,r11} @ x4 + + add r12, r4, r10 @ s7.r = x1.r + x4.r + sub r4, r4, r10 @ s10.r = x1.r - x4.r + add r10, r5, r11 @ s7.i + sub r5, r5, r11 @ s10.i + add r11, r6, r8 @ s8.r = x2.r + x3.r + sub r6, r6, r8 @ s9.r = x2.r - x3.r + add r8, r7, r9 @ s8.i + sub r7, r7, r9 @ s9.i + + /* s4 *= sqrt(5)/4. yc borrows ya.i's register; one ldr puts it back, + which is cheaper than spending a register on a constant used twice. + It loads ahead of the four sums below, which is what keeps its I + cycle off the multiply. */ + ldr r1, .Lpfa_yc + add r9, r12, r11 @ s3.r = s7.r + s8.r + sub r12, r12, r11 @ s4.r = s7.r - s8.r + add r11, r10, r8 @ s3.i + sub r10, r10, r8 @ s4.i + + smull r8, lr, r12, r1 + QNARROW r12, r8, lr @ s4.r + smull r8, lr, r10, r1 + QNARROW r10, r8, lr @ s4.i + ldr lr, [sp, #(\voff+0)] @ x0.r + ldr r1, .Lpfa_yai @ and this covers its I cycle + + /* X0 = x0 + s3 goes out immediately, then s3 becomes x0 - s3/4. x0.r + is loaded a line early so the constant reload above covers it, and + x0.i goes into the scratch that storing X0.r frees, which puts three + instructions between that load and its use. */ + add r8, lr, r9 @ X0.r + str r8, [r0] + ldr r8, [sp, #(\voff+4)] @ x0.i + add r9, r9, #2 + sub r9, lr, r9, asr #2 @ QUARTER_OF rounds, as kf_bfly5 does + add lr, r8, r11 @ X0.i + str lr, [r0, #4] + add r11, r11, #2 + sub r11, r8, r11, asr #2 + + /* s5 and s11 are not touched by the eight multiplies below and there is + no register to keep them in, so they wait on the frame -- sixteen + bytes, the same line every call. */ + add r8, r9, r12 @ s5.r + sub r9, r9, r12 @ s11.r + add lr, r11, r10 @ s5.i + sub r11, r11, r10 @ s11.i + mov r10, lr @ stm stores in register order, not + add r12, sp, #PARK @ list order, so s5.i must sit in r10 + stmia r12, {r8, r9, r10, r11} + + /* s6.r = s10.i*ya.i + s9.i*yb.i s12.r = s9.i*ya.i - s10.i*yb.i */ + smull r10, r12, r5, r1 + QNARROW r8, r10, r12 + smull r10, r12, r7, r2 + QNARROW r9, r10, r12 + smull r10, r12, r7, r1 + QNARROW r11, r10, r12 + smull r10, r12, r5, r2 + QNARROW lr, r10, r12 + add r5, r8, r9 @ s6.r + sub r7, r11, lr @ s12.r + + /* s6.i = -(s10.r*ya.i + s9.r*yb.i) s12.i = s10.r*yb.i - s9.r*ya.i */ + smull r10, r12, r4, r1 + QNARROW r8, r10, r12 + smull r10, r12, r6, r2 + QNARROW r9, r10, r12 + smull r10, r12, r4, r2 + QNARROW r11, r10, r12 + smull r10, r12, r6, r1 + QNARROW lr, r10, r12 + add r4, r8, r9 + rsb r4, r4, #0 @ s6.i + sub r6, r11, lr @ s12.i + + add r12, sp, #PARK + ldmia r12, {r8, r9, r10, r11} @ s5.r, s11.r, s5.i, s11.i + + sub r12, r8, r5 @ X1 = s5 - s6 + sub lr, r10, r4 + PFA_ADV \d1 + stmia r0, {r12, lr} + + add r12, r8, r5 @ X4 = s5 + s6 + add lr, r10, r4 + PFA_ADV \d2 + stmia r0, {r12, lr} + + add r12, r9, r7 @ X2 = s11 + s12 + add lr, r11, r6 + PFA_ADV \d3 + stmia r0, {r12, lr} + + sub r12, r9, r7 @ X3 = s11 - s12 + sub lr, r11, r6 + PFA_ADV \d4 + stmia r0, {r12, lr} + .endm + +/* ------------------------------------------------------------------------ + * void pfa_fft15_armv4(const kiss_fft_cpx *in, kiss_fft_cpx *out, + * int ostride) + * + * r0 = in (15 contiguous complex) r1 = out r2 = ostride, in elements + * ------------------------------------------------------------------------ */ + .global pfa_fft15_armv4 + .type pfa_fft15_armv4, %function +pfa_fft15_armv4: + push {r4-r11, lr} + sub sp, sp, #FRAME + + mov r2, r2, lsl #3 @ ostride in bytes + str r1, [sp, #SV_OUT] + add r3, r2, r2, lsl #1 @ 3 elements + str r3, [sp, #SV_STEP3] + add r3, r2, r2, lsl #2 @ 5 elements + str r3, [sp, #SV_FIVE] + +/* ---- Five 3-point DFTs ------------------------------------------------- + * Group g reads in[3g..3g+2] and writes v[g], v[5+g], v[10+g], so the + * 5-point DFTs below each read a contiguous run of five. + */ + add r1, sp, #VOFF @ &v[0] + add r2, sp, #(VOFF+40) @ &v[5] + add r3, sp, #(VOFF+80) @ &v[10] + ldr lr, .Lpfa_epi3 + mov r12, #5 + +.Lpfa_b3: + ldmia r0!, {r4,r5,r6,r7,r8,r9} @ x0, x1, x2 + add r10, r6, r8 @ s3.r = x1.r + x2.r + sub r6, r6, r8 @ s0.r = x1.r - x2.r + add r11, r7, r9 @ s3.i + sub r7, r7, r9 @ s0.i + sub r8, r4, r10, asr #1 @ xm.r = x0.r - HALF_OF(s3.r) + sub r9, r5, r11, asr #1 @ xm.i + add r4, r4, r10 @ X0.r = x0.r + s3.r + add r5, r5, r11 @ X0.i + smull r10, r11, r6, lr @ s0.r * epi3 + QNARROW r6, r10, r11 + smull r10, r11, r7, lr @ s0.i * epi3 + QNARROW r7, r10, r11 + stmia r1!, {r4,r5} @ v[g] + sub r10, r8, r7 + add r11, r9, r6 + stmia r2!, {r10,r11} @ v[5+g] + add r10, r8, r7 + sub r11, r9, r6 + stmia r3!, {r10,r11} @ v[10+g] + subs r12, r12, #1 + bne .Lpfa_b3 + +/* ---- Three 5-point DFTs ------------------------------------------------ */ + ldr r1, .Lpfa_yai + ldr r2, .Lpfa_ybi + ldr r3, [sp, #SV_STEP3] + + ldr r0, [sp, #SV_OUT] @ k1 = 0: base is out itself + PFA_DFT5 (VOFF+0), 2, 1, 1, -3 + + ldr r0, [sp, #SV_OUT] + ldr r12, [sp, #SV_FIVE] + add r0, r0, r12, lsl #1 @ k1 = 1: base is out + 10 elements + PFA_DFT5 (VOFF+40), -3, 1, 1, 2 + + ldr r0, [sp, #SV_OUT] + ldr r12, [sp, #SV_FIVE] + add r0, r0, r12 @ k1 = 2: base is out + 5 elements + PFA_DFT5 (VOFF+80), 2, 1, -4, 2 + + add sp, sp, #FRAME + pop {r4-r11, pc} + .size pfa_fft15_armv4, .-pfa_fft15_armv4 + + .align 2 +.Lpfa_epi3: + .word -28378 @ -sin(2*pi/3), as kf_bfly3 uses +.Lpfa_yai: + .word -31164 @ ya.i +.Lpfa_ybi: + .word -19261 @ yb.i +.Lpfa_yc: + .word 18318 @ sqrt(5)/4 diff --git a/lib/rbcodec/codecs/libopus/celt/arm/pfa_armv5e_asm.S b/lib/rbcodec/codecs/libopus/celt/arm/pfa_armv5e_asm.S new file mode 100644 index 0000000000..21d1419086 --- /dev/null +++ b/lib/rbcodec/codecs/libopus/celt/arm/pfa_armv5e_asm.S @@ -0,0 +1,227 @@ +/* ARMv5E Good-Thomas 15-point DFT for the CELT backward MDCT. + * + * Copyright (c) 2026 Michael Giacomelli + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the conditions stated in + * celt/kiss_fft.c are met. + * + * Why this exists + * --------------- + * The same transform as pfa_armv4_asm.S -- five 3-point DFTs then three + * 5-point DFTs, co-prime so nothing sits between them -- but this core + * changes what the arithmetic costs, and with it the register budget. + * + * A Q15 narrowing is one smulwb rather than smull/shift/orr, so each of the + * forty multiplies drops from three instructions to one and needs one + * scratch register rather than a pair. Where the C adds two products, + * smlawb folds the add in and is bit-exact with it, because both truncate + * at Q16 before the sum and the shift distributes. Where it subtracts them + * the two stay separate: smlaw only adds, so fusing would mean negating a + * twiddle, and truncation is not symmetric about zero. + * + * The Q15 doubling is deferred. MULT16_32_Q15 on this core is smulwb + * followed by a shift left, and every consumer of a product here is an add + * or a subtract, so the shift folds into it as a free immediate. That is + * exact rather than approximate: a +/- (b << 1) is the same value as + * a +/- SHL32(b, 1) in all 32 bits. + * + * Between them those two savings free enough registers that the 5-point DFT + * needs no frame at all -- where the ARMv4 version has to park its sum and + * difference pair across the multiplies, this one keeps everything live. + * ya.i and yb.i share one word, picked apart by the b and t halves of + * smulwb and smulwt, which is the same packing the radix-5 pass in + * kiss_fft_armv5e_asm.S uses. + */ + +#if defined(__thumb__) || defined(__thumb2__) +#error "pfa_armv5e_asm.S must be assembled in ARM mode" +#endif + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + + .text + .align 2 + +#define VOFF 0 +#define SV_OUT 120 +#define SV_STEP3 124 +#define SV_FIVE 128 +#define FRAME 136 + +/* Advance the output pointer by d groups of three elements; see the ARMv4 + kernel for why every delta is a small multiple of one stride. */ + .macro PFA5_ADV d + .if \d == 2 + add r0, r0, r3, lsl #1 + .elseif \d == 1 + add r0, r0, r3 + .elseif \d == -3 + sub r0, r0, r3, lsl #1 + sub r0, r0, r3 + .elseif \d == -4 + sub r0, r0, r3, lsl #2 + .else + .error "unexpected PFA store delta" + .endif + .endm + +/* One 5-point DFT over v[voff..voff+4]. + * r0 = output, at this DFT's base r1 = ya.i packed with yb.i + * r2 = sqrt(5)/4 r3 = three output elements, in bytes + */ + .macro PFA5_DFT5 voff, d1, d2, d3, d4 + add r12, sp, #(\voff+8) + ldmia r12, {r4,r5,r6,r7,r8,r9} @ x1, x2, x3 + add r12, sp, #(\voff+32) + ldm r12, {r10,r11} @ x4 + + add r12, r4, r10 @ s7.r = x1.r + x4.r + sub r4, r4, r10 @ s10.r = x1.r - x4.r + add r10, r5, r11 @ s7.i + sub r5, r5, r11 @ s10.i + add r11, r6, r8 @ s8.r = x2.r + x3.r + sub r6, r6, r8 @ s9.r = x2.r - x3.r + add r8, r7, r9 @ s8.i + sub r7, r7, r9 @ s9.i + + add r9, r12, r11 @ s3.r = s7.r + s8.r + sub r12, r12, r11 @ s4.r = s7.r - s8.r + add r11, r10, r8 @ s3.i + sub r10, r10, r8 @ s4.i + + ldr lr, [sp, #(\voff+0)] @ x0.r, ahead of the two multiplies + smulwb r12, r12, r2 @ s4.r * sqrt(5)/4, left at Q16 + smulwb r10, r10, r2 @ s4.i * sqrt(5)/4 + + add r8, lr, r9 @ X0.r + str r8, [r0] + ldr r8, [sp, #(\voff+4)] @ x0.i, into the scratch that frees + add r9, r9, #2 + sub r9, lr, r9, asr #2 @ QUARTER_OF rounds, as kf_bfly5 does + add lr, r8, r11 @ X0.i + str lr, [r0, #4] + add r11, r11, #2 + sub r11, r8, r11, asr #2 + + add r8, r9, r12, lsl #1 @ s5.r (the deferred doubling) + sub r9, r9, r12, lsl #1 @ s11.r + add lr, r11, r10, lsl #1 @ s5.i + sub r11, r11, r10, lsl #1 @ s11.i + + /* s6.r = s10.i*ya + s9.i*yb, so smlawt carries the add. */ + smulwb r10, r5, r1 + smlawt r10, r7, r1, r10 + /* s12.r = s9.i*ya - s10.i*yb: a difference, so the products stay apart. */ + smulwb r12, r7, r1 + smulwt r5, r5, r1 + sub r12, r12, r5 + + /* s6.i is the negation of this sum; the sign folds into the consumers. */ + smulwb r5, r4, r1 + smlawt r5, r6, r1, r5 + /* s12.i = s10.r*yb - s9.r*ya */ + smulwt r4, r4, r1 + smulwb r6, r6, r1 + sub r4, r4, r6 + + sub r6, r8, r10, lsl #1 @ X1.r = s5.r - s6.r + add r7, lr, r5, lsl #1 @ X1.i = s5.i - s6.i + PFA5_ADV \d1 + stmia r0, {r6, r7} + + add r6, r8, r10, lsl #1 @ X4.r + sub r7, lr, r5, lsl #1 @ X4.i + PFA5_ADV \d2 + stmia r0, {r6, r7} + + add r6, r9, r12, lsl #1 @ X2.r = s11.r + s12.r + add r7, r11, r4, lsl #1 @ X2.i + PFA5_ADV \d3 + stmia r0, {r6, r7} + + sub r6, r9, r12, lsl #1 @ X3.r + sub r7, r11, r4, lsl #1 @ X3.i + PFA5_ADV \d4 + stmia r0, {r6, r7} + .endm + +/* ------------------------------------------------------------------------ + * void pfa_fft15_armv5e(const kiss_fft_cpx *in, kiss_fft_cpx *out, + * int ostride) + * ------------------------------------------------------------------------ */ + .global pfa_fft15_armv5e + .type pfa_fft15_armv5e, %function +pfa_fft15_armv5e: + push {r4-r11, lr} + sub sp, sp, #FRAME + + mov r2, r2, lsl #3 @ ostride in bytes + str r1, [sp, #SV_OUT] + add r3, r2, r2, lsl #1 + str r3, [sp, #SV_STEP3] + add r3, r2, r2, lsl #2 + str r3, [sp, #SV_FIVE] + +/* ---- Five 3-point DFTs ------------------------------------------------ */ + add r1, sp, #VOFF + add r2, sp, #(VOFF+40) + add r3, sp, #(VOFF+80) + ldr lr, .Lpfa5_epi3 + mov r12, #5 + +.Lpfa5_b3: + ldmia r0!, {r4,r5,r6,r7,r8,r9} @ x0, x1, x2 + add r10, r6, r8 @ s3.r + sub r6, r6, r8 @ s0.r + add r11, r7, r9 @ s3.i + sub r7, r7, r9 @ s0.i + sub r8, r4, r10, asr #1 @ xm.r = x0.r - HALF_OF(s3.r) + sub r9, r5, r11, asr #1 @ xm.i + add r4, r4, r10 @ X0.r + add r5, r5, r11 @ X0.i + smulwb r6, r6, lr @ s0.r * epi3, left at Q16 + smulwb r7, r7, lr @ s0.i * epi3 + stmia r1!, {r4,r5} @ v[g] + sub r10, r8, r7, lsl #1 + add r11, r9, r6, lsl #1 + stmia r2!, {r10,r11} @ v[5+g] + add r10, r8, r7, lsl #1 + sub r11, r9, r6, lsl #1 + stmia r3!, {r10,r11} @ v[10+g] + subs r12, r12, #1 + bne .Lpfa5_b3 + +/* ---- Three 5-point DFTs ----------------------------------------------- */ + ldr r1, .Lpfa5_yab + ldr r2, .Lpfa5_yc + ldr r3, [sp, #SV_STEP3] + + ldr r0, [sp, #SV_OUT] + PFA5_DFT5 (VOFF+0), 2, 1, 1, -3 + + ldr r0, [sp, #SV_OUT] + ldr r12, [sp, #SV_FIVE] + add r0, r0, r12, lsl #1 + PFA5_DFT5 (VOFF+40), -3, 1, 1, 2 + + ldr r0, [sp, #SV_OUT] + ldr r12, [sp, #SV_FIVE] + add r0, r0, r12 + PFA5_DFT5 (VOFF+80), 2, 1, -4, 2 + + add sp, sp, #FRAME + pop {r4-r11, pc} + .size pfa_fft15_armv5e, .-pfa_fft15_armv5e + + .align 2 +.Lpfa5_epi3: + .word -28378 @ -sin(2*pi/3), in the low half +.Lpfa5_yab: + .word 0xb4c38644 @ ya.i = -31164 low, yb.i = -19261 top +.Lpfa5_yc: + .word 18318 @ sqrt(5)/4 + + .section .note.GNU-stack,"",%progbits diff --git a/lib/rbcodec/codecs/libopus/celt/arm/pitch_arm.h b/lib/rbcodec/codecs/libopus/celt/arm/pitch_arm.h index bed8b04eac..39679978f2 100644 --- a/lib/rbcodec/codecs/libopus/celt/arm/pitch_arm.h +++ b/lib/rbcodec/codecs/libopus/celt/arm/pitch_arm.h @@ -66,6 +66,23 @@ extern void (*const DUAL_INNER_PROD_IMPL[OPUS_ARCHMASK+1])(const opus_val16 *x, # endif # endif +/* The decoder reaches dual_inner_prod from one place, stereo_merge. The C + reads three signed halfwords per element and stalls on each of them, since + an LDRSH result lands two cycles late on this core; one LDR fetches two + coefficients instead and smlabb/smlatt take the halves apart with no sign + extension of their own. Measured at 12.1 cycles per element before and + 5.5 after. ARMv4 is deliberately left alone: without the packed + multiplies the extraction would cost exactly what the saved loads buy. + Escape hatch OPUS_ARM_NO_PITCH_ASM. */ +# if defined(OPUS_ARM_ASM_ARMV5E_AND_LATER) && defined(FIXED_POINT) \ + && !defined(OVERRIDE_DUAL_INNER_PROD) && !defined(OPUS_ARM_NO_PITCH_ASM) +void dual_inner_prod_armv5e(const opus_val16 *x, const opus_val16 *y01, + const opus_val16 *y02, int N, opus_val32 *xy1, opus_val32 *xy2); +# define OVERRIDE_DUAL_INNER_PROD (1) +# define dual_inner_prod(x, y01, y02, N, xy1, xy2, arch) \ + ((void)(arch), dual_inner_prod_armv5e(x, y01, y02, N, xy1, xy2)) +# endif + # if defined(FIXED_POINT) # if defined(OPUS_ARM_MAY_HAVE_NEON) diff --git a/lib/rbcodec/codecs/libopus/celt/arm/pitch_armv5e_asm.S b/lib/rbcodec/codecs/libopus/celt/arm/pitch_armv5e_asm.S new file mode 100644 index 0000000000..ad271e9dc8 --- /dev/null +++ b/lib/rbcodec/codecs/libopus/celt/arm/pitch_armv5e_asm.S @@ -0,0 +1,121 @@ +/* ARMv5E dual_inner_prod for the CELT fixed-point decoder. + * + * Copyright (c) 2007-2008 CSIRO + * Copyright (c) 2007-2009 Xiph.Org Foundation + * Copyright (c) 2026 Michael Giacomelli + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the conditions stated in + * celt/pitch.h are met. + * + * Why this exists + * --------------- + * The decoder reaches dual_inner_prod from one place, stereo_merge, which + * calls it as dual_inner_prod(Y, X, Y, N, &xp, &side). Over eight packets of + * stereo decode that is 6,400 elements costing 12.1 cycles each, against two + * multiplies of real work. + * + * The cost is the loads. The C reads three signed halfwords per element, and + * on ARM926EJ-S an LDRSH result is two cycles late rather than one, so the + * loop stalls on every one of them. One LDR fetches two coefficients + * instead, and smlabb and smlatt take the halves apart without a separate + * sign extension -- which is also why this is worth doing here and not on + * ARMv4, where the extraction would cost exactly what the extra loads save. + * + * Alignment. A packed load needs a word-aligned address, and celt_norm is a + * halfword, so a band can start on an odd element. The three pointers can + * only be packed together if they share their alignment, so the kernel checks + * that first and falls back to the scalar loop if they disagree; when they + * agree it steps one element at a time until the address is word-aligned and + * then runs packed. Both fallbacks compute exactly what the fast path does, + * in the same order. + * + * Bit-exactness. MAC16_16 is smlabb and the sum is a plain 32-bit + * accumulation, so the order of accumulation is the only thing that could + * differ, and this keeps it: element 0 then 1 within each word, low + * addresses first. Output is bit-identical to the C, which the test harness + * checks sample by sample. + */ + +#if defined(__thumb__) || defined(__thumb2__) +#error "pitch_armv5e_asm.S must be assembled in ARM mode" +#endif + + .text + .align 2 + +/* ------------------------------------------------------------------------ + * void dual_inner_prod_armv5e(const opus_val16 *x, const opus_val16 *y01, + * const opus_val16 *y02, int N, + * opus_val32 *xy1, opus_val32 *xy2) + * + * r0 = x r1 = y01 r2 = y02 r3 = N [sp+0] = xy1 [sp+4] = xy2 + * ------------------------------------------------------------------------ */ + .global dual_inner_prod_armv5e + .type dual_inner_prod_armv5e, %function +dual_inner_prod_armv5e: + push {r4-r8, lr} + mov r7, #0 @ xy01 + mov r8, #0 @ xy02 + cmp r3, #0 + ble .Ldip_done + + /* The three streams can only be read in pairs if they agree on their + alignment; two bits of exclusive-or settle it. */ + eor ip, r0, r1 + eor lr, r0, r2 + orr ip, ip, lr + tst ip, #3 + bne .Ldip_scalar + +.Ldip_align: @ step singly until x is word-aligned + tst r0, #3 + beq .Ldip_pairs + ldrsh r4, [r0], #2 + ldrsh r5, [r1], #2 + ldrsh r6, [r2], #2 + smlabb r7, r4, r5, r7 + smlabb r8, r4, r6, r8 + subs r3, r3, #1 + bne .Ldip_align + b .Ldip_done + +.Ldip_pairs: + subs r3, r3, #2 + bmi .Ldip_tail +.Ldip_loop: + ldr r4, [r0], #4 @ x[i], x[i+1] + ldr r5, [r1], #4 @ y01[i], y01[i+1] + ldr r6, [r2], #4 @ y02[i], y02[i+1] + smlabb r7, r4, r5, r7 + smlabb r8, r4, r6, r8 + smlatt r7, r4, r5, r7 + smlatt r8, r4, r6, r8 + subs r3, r3, #2 + bpl .Ldip_loop +.Ldip_tail: + tst r3, #1 @ one element left when N was odd + beq .Ldip_done + ldrsh r4, [r0] + ldrsh r5, [r1] + ldrsh r6, [r2] + smlabb r7, r4, r5, r7 + smlabb r8, r4, r6, r8 + b .Ldip_done + +.Ldip_scalar: + ldrsh r4, [r0], #2 + ldrsh r5, [r1], #2 + ldrsh r6, [r2], #2 + smlabb r7, r4, r5, r7 + smlabb r8, r4, r6, r8 + subs r3, r3, #1 + bne .Ldip_scalar + +.Ldip_done: + ldr ip, [sp, #24] @ xy1 + ldr lr, [sp, #28] @ xy2 + str r7, [ip] + str r8, [lr] + pop {r4-r8, pc} + .size dual_inner_prod_armv5e, .-dual_inner_prod_armv5e diff --git a/lib/rbcodec/codecs/libopus/celt/arm/vq_arm.h b/lib/rbcodec/codecs/libopus/celt/arm/vq_arm.h new file mode 100644 index 0000000000..98f00baee9 --- /dev/null +++ b/lib/rbcodec/codecs/libopus/celt/arm/vq_arm.h @@ -0,0 +1,65 @@ +/* Copyright (c) 2025 Xiph.Org Foundation and contributors + Copyright (c) 2026 Michael Giacomelli + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef VQ_ARM_H +#define VQ_ARM_H + +/* Hand-written loops for celt/vq.c, through the OVERRIDE_vq_exp_rotation1 + hook vq.c already provides. Both architectures have one: the rotation + chain writes X[i+stride] and reads it straight back as the next x1, so at + stride 1 one of the two loads is redundant and one of the two stores is + dead, and both kernels carry that value in a register instead. Bit-exact; + see celt/arm/exp_rotation1_armv5e_asm.S and _armv4_asm.S. + + Shares OPUS_ARM_NO_BANDS_ASM with arm/bands_arm.h as the escape hatch. */ + +#if defined(FIXED_POINT) && !defined(OPUS_ARM_NO_BANDS_ASM) \ + && defined(OPUS_ARM_ASM_ARMV5E_AND_LATER) && (ARM_ARCH >= 5) + +#define OVERRIDE_vq_exp_rotation1 +void exp_rotation1_armv5e(celt_norm *X, int len, int stride, opus_val16 c, + opus_val16 s); +#define exp_rotation1(X, len, stride, c, s) \ + exp_rotation1_armv5e((X), (len), (stride), (c), (s)) + +/* The scaling loop at the end of normalise_residual. ARMv5E only: it wants + smulwb. See celt/arm/normres_armv5e_asm.S. */ +#define OVERRIDE_NORMRES_SCALE +void normres_scale_armv5e(celt_norm *X, const int *iy, int N, int g, + int shift); +#define NORMRES_SCALE normres_scale_armv5e + +#elif defined(FIXED_POINT) && !defined(OPUS_ARM_NO_BANDS_ASM) \ + && defined(OPUS_ARM_ASM_ARMV4_ONLY) && (ARM_ARCH == 4) + +#define OVERRIDE_vq_exp_rotation1 +void exp_rotation1_armv4(celt_norm *X, int len, int stride, opus_val16 c, + opus_val16 s); +#define exp_rotation1(X, len, stride, c, s) \ + exp_rotation1_armv4((X), (len), (stride), (c), (s)) + +#endif + +#endif /* VQ_ARM_H */ diff --git a/lib/rbcodec/codecs/libopus/celt/bands.c b/lib/rbcodec/codecs/libopus/celt/bands.c index 186468568c..3f95dd25af 100644 --- a/lib/rbcodec/codecs/libopus/celt/bands.c +++ b/lib/rbcodec/codecs/libopus/celt/bands.c @@ -33,6 +33,18 @@ #include #include "bands.h" +#include "log2tan_table.h" + +/* Rockbox builds the decoder only: celt_encoder.c is not in SOURCES, and the + one caller of quant_all_bands passes encode=0. Saying so lets gcc fold the + thirteen encoder-only branches in this file away -- and, worth more than the + branches, stops it keeping their live values in registers across the + recursive calls, which is where quant_partition was spilling. */ +#ifdef CELT_DECODE_ONLY +# define CELT_ENCODE_FLAG(x) 0 +#else +# define CELT_ENCODE_FLAG(x) (x) +#endif #include "modes.h" #include "vq.h" #include "cwrs.h" @@ -42,6 +54,9 @@ #include "rate.h" #include "quant_bands.h" #include "pitch.h" +#if defined(OPUS_ARM_ASM) +#include "arm/bands_arm.h" +#endif int hysteresis_decision(opus_val16 val, const opus_val16 *thresholds, const opus_val16 *hysteresis, int N, int prev) { @@ -77,7 +92,7 @@ opus_int16 bitexact_cos(opus_int16 x) return 1+x2; } -int bitexact_log2tan(int isin,int icos) +ICODE_ATTR_OPUS_MR int bitexact_log2tan(int isin,int icos) { int lc; int ls; @@ -193,7 +208,7 @@ void normalise_bands(const CELTMode *m, const celt_sig * OPUS_RESTRICT freq, cel #endif /* FIXED_POINT */ /* De-normalise the energy to produce the synthesis from the unit-energy bands */ -void denormalise_bands(const CELTMode *m, const celt_norm * OPUS_RESTRICT X, +ICODE_ATTR_OPUS void denormalise_bands(const CELTMode *m, const celt_norm * OPUS_RESTRICT X, celt_sig * OPUS_RESTRICT freq, const opus_val16 *bandLogE, int start, int end, int M, int downsample, int silence) { @@ -256,16 +271,24 @@ void denormalise_bands(const CELTMode *m, const celt_norm * OPUS_RESTRICT X, } else #endif /* Be careful of the fixed-point "else" just above when changing this code */ +#if defined(FIXED_POINT) && defined(OVERRIDE_DENORM_BAND) + { + DENORM_BAND(f, x, band_end-j, g, shift); + f += band_end-j; + x += band_end-j; + } +#else do { *f++ = SHR32(MULT16_16(*x++, g), shift); } while (++j>= 1; @@ -643,6 +667,7 @@ void haar1(celt_norm *X, int N0, int stride) X[stride*(2*j+1)+i] = EXTRACT16(PSHR32(SUB32(tmp1, tmp2), 15)); } } +#endif static int compute_qn(int N, int b, int offset, int pulse_cap, int stereo) { @@ -697,13 +722,16 @@ struct split_ctx { int qalloc; }; -static void compute_theta(struct band_ctx *ctx, struct split_ctx *sctx, +ICODE_ATTR_OPUS static void compute_theta(struct band_ctx *ctx, struct split_ctx *sctx, celt_norm *X, celt_norm *Y, int N, int *b, int B, int B0, int LM, int stereo, int *fill) { int qn; int itheta=0; +#ifdef OPUS_LOG2TAN_TABLE + int itheta_q=-1; /* the quantiser index behind itheta */ +#endif int delta; int imid, iside; int qalloc; @@ -718,7 +746,7 @@ static void compute_theta(struct band_ctx *ctx, struct split_ctx *sctx, ec_ctx *ec; const celt_ener *bandE; - encode = ctx->encode; + encode = CELT_ENCODE_FLAG(ctx->encode); m = ctx->m; i = ctx->i; intensity = ctx->intensity; @@ -836,6 +864,9 @@ static void compute_theta(struct band_ctx *ctx, struct split_ctx *sctx, } } celt_assert(itheta>=0); +#ifdef OPUS_LOG2TAN_TABLE + itheta_q = itheta; +#endif itheta = celt_udiv((opus_int32)itheta*16384, qn); if (encode && stereo) { @@ -891,7 +922,29 @@ static void compute_theta(struct band_ctx *ctx, struct split_ctx *sctx, iside = bitexact_cos((opus_int16)(16384-itheta)); /* This is the mid vs side allocation that minimizes squared error in that band. */ +#ifdef OPUS_LOG2TAN_TABLE + /* bitexact_log2tan here is a function of (qn, itheta_q) alone, and that + set is closed and small, so it is tabulated exactly: two EC_ILOG + ladders and four FRAC_MUL16 products become one indexed load. The + bounds test is what makes the lookup safe rather than merely correct: + every decode path bounds itheta_q to [0,qn], but a table index must + not depend on that being true of a corrupt stream. */ + { + /* qn needs no range test: compute_qn clamps qb to 8<inv = inv; @@ -912,7 +965,7 @@ static unsigned quant_band_n1(struct band_ctx *ctx, celt_norm *X, celt_norm *Y, (void)b; - encode = ctx->encode; + encode = CELT_ENCODE_FLAG(ctx->encode); ec = ctx->ec; stereo = Y != NULL; @@ -943,7 +996,7 @@ static unsigned quant_band_n1(struct band_ctx *ctx, celt_norm *X, celt_norm *Y, It can split the band in two and transmit the energy difference with the two half-bands. It can be called recursively so bands can end up being split in 8 parts. */ -static unsigned quant_partition(struct band_ctx *ctx, celt_norm *X, +ICODE_ATTR_OPUS static unsigned quant_partition(struct band_ctx *ctx, celt_norm *X, int N, int b, int B, celt_norm *lowband, int LM, opus_val16 gain, int fill) @@ -962,7 +1015,7 @@ static unsigned quant_partition(struct band_ctx *ctx, celt_norm *X, int spread; ec_ctx *ec; - encode = ctx->encode; + encode = CELT_ENCODE_FLAG(ctx->encode); m = ctx->m; i = ctx->i; spread = ctx->spread; @@ -1109,7 +1162,7 @@ static unsigned quant_partition(struct band_ctx *ctx, celt_norm *X, /* This function is responsible for encoding and decoding a band for the mono case. */ -static unsigned quant_band(struct band_ctx *ctx, celt_norm *X, +ICODE_ATTR_OPUS static unsigned quant_band(struct band_ctx *ctx, celt_norm *X, int N, int b, int B, celt_norm *lowband, int LM, celt_norm *lowband_out, opus_val16 gain, celt_norm *lowband_scratch, int fill) @@ -1126,7 +1179,7 @@ static unsigned quant_band(struct band_ctx *ctx, celt_norm *X, int encode; int tf_change; - encode = ctx->encode; + encode = CELT_ENCODE_FLAG(ctx->encode); tf_change = ctx->tf_change; longBlocks = B0==1; @@ -1235,7 +1288,7 @@ static unsigned quant_band(struct band_ctx *ctx, celt_norm *X, /* This function is responsible for encoding and decoding a band for the stereo case. */ -static unsigned quant_band_stereo(struct band_ctx *ctx, celt_norm *X, celt_norm *Y, +ICODE_ATTR_OPUS static unsigned quant_band_stereo(struct band_ctx *ctx, celt_norm *X, celt_norm *Y, int N, int b, int B, celt_norm *lowband, int LM, celt_norm *lowband_out, celt_norm *lowband_scratch, int fill) @@ -1252,7 +1305,7 @@ static unsigned quant_band_stereo(struct band_ctx *ctx, celt_norm *X, celt_norm int encode; ec_ctx *ec; - encode = ctx->encode; + encode = CELT_ENCODE_FLAG(ctx->encode); ec = ctx->ec; /* Special case for one sample */ @@ -1396,7 +1449,7 @@ static void special_hybrid_folding(const CELTMode *m, celt_norm *norm, celt_norm OPUS_COPY(&norm2[n1], &norm2[2*n1 - n2], n2-n1); } -void quant_all_bands(int encode, const CELTMode *m, int start, int end, +ICODE_ATTR_OPUS void quant_all_bands(int encode, const CELTMode *m, int start, int end, celt_norm *X_, celt_norm *Y_, unsigned char *collapse_masks, const celt_ener *bandE, int *pulses, int shortBlocks, int spread, int dual_stereo, int intensity, int *tf_res, opus_int32 total_bits, @@ -1461,6 +1514,7 @@ void quant_all_bands(int encode, const CELTMode *m, int start, int end, lowband_offset = 0; ctx.bandE = bandE; ctx.ec = ec; + encode = CELT_ENCODE_FLAG(encode); ctx.encode = encode; ctx.intensity = intensity; ctx.m = m; diff --git a/lib/rbcodec/codecs/libopus/celt/celt.c b/lib/rbcodec/codecs/libopus/celt/celt.c index 9ce234695c..633e932cd5 100644 --- a/lib/rbcodec/codecs/libopus/celt/celt.c +++ b/lib/rbcodec/codecs/libopus/celt/celt.c @@ -187,7 +187,7 @@ void comb_filter_const_c(opus_val32 *y, opus_val32 *x, int T, int N, #endif #ifndef OVERRIDE_comb_filter -void comb_filter(opus_val32 *y, opus_val32 *x, int T0, int T1, int N, +ICODE_ATTR_OPUS void comb_filter(opus_val32 *y, opus_val32 *x, int T0, int T1, int N, opus_val16 g0, opus_val16 g1, int tapset0, int tapset1, const opus_val16 *window, int overlap, int arch) { diff --git a/lib/rbcodec/codecs/libopus/celt/celt.h b/lib/rbcodec/codecs/libopus/celt/celt.h index 24b6b2b520..90a464e39b 100644 --- a/lib/rbcodec/codecs/libopus/celt/celt.h +++ b/lib/rbcodec/codecs/libopus/celt/celt.h @@ -230,6 +230,10 @@ void comb_filter_const_c(opus_val32 *y, opus_val32 *x, int T, int N, opus_val16 g10, opus_val16 g11, opus_val16 g12); #endif +#if defined(OPUS_ARM_ASM) +#include "arm/comb_filter_arm.h" +#endif + #ifndef OVERRIDE_COMB_FILTER_CONST # define comb_filter_const(y, x, T, N, g10, g11, g12, arch) \ ((void)(arch),comb_filter_const_c(y, x, T, N, g10, g11, g12)) diff --git a/lib/rbcodec/codecs/libopus/celt/celt_decoder.c b/lib/rbcodec/codecs/libopus/celt/celt_decoder.c index e6efce9358..6e36ee4d1c 100644 --- a/lib/rbcodec/codecs/libopus/celt/celt_decoder.c +++ b/lib/rbcodec/codecs/libopus/celt/celt_decoder.c @@ -227,6 +227,7 @@ void opus_custom_decoder_destroy(CELTDecoder *st) /* Special case for stereo with no downsampling and no accumulation. This is quite common and we can make it faster by processing both channels in the same loop, reducing overhead due to the dependency loop in the IIR filter. */ +#ifndef OVERRIDE_DEEMPH_STEREO static void deemphasis_stereo_simple(celt_sig *in[], opus_val16 *pcm, int N, const opus_val16 coef0, celt_sig *mem) { @@ -253,6 +254,7 @@ static void deemphasis_stereo_simple(celt_sig *in[], opus_val16 *pcm, int N, con mem[1] = m1; } #endif +#endif #ifndef RESYNTH static @@ -357,10 +359,20 @@ void deemphasis(celt_sig *in[], opus_val16 *pcm, int N, int C, int downsample, c RESTORE_STACK; } +#ifndef OVERRIDE_CELT_SAT +/* Clamp n samples to +/-SIG_SAT in place. */ +static OPUS_INLINE void celt_sat(celt_sig *x, int n) +{ + int i; + for (i=0;i=0;i--) + { + opus_val32 sum = SHL32(EXTEND32(x[i]), SIG_SHIFT); + for (j=0;jwindow; c=0; do { @@ -671,11 +693,9 @@ static void celt_decode_lost(CELTDecoder * OPUS_RESTRICT st, int N, int LM) /* Initialize the LPC history with the samples just before the start of the region for which we're computing the excitation. */ { - /* Compute the excitation for exc_length samples before the loss. We need the copy - because celt_fir() cannot filter in-place. */ - celt_fir(exc+MAX_PERIOD-exc_length, lpc+c*LPC_ORDER, - fir_tmp, exc_length, LPC_ORDER, st->arch); - OPUS_COPY(exc+MAX_PERIOD-exc_length, fir_tmp, exc_length); + /* Compute the excitation for exc_length samples before the loss. */ + celt_fir_inplace(exc+MAX_PERIOD-exc_length, lpc+c*LPC_ORDER, + exc_length, LPC_ORDER); } /* Check if the waveform is decaying, and if so how fast. @@ -811,7 +831,7 @@ static void celt_decode_lost(CELTDecoder * OPUS_RESTRICT st, int N, int LM) RESTORE_STACK; } -int celt_decode_with_ec(CELTDecoder * OPUS_RESTRICT st, const unsigned char *data, +ICODE_ATTR_OPUS int celt_decode_with_ec(CELTDecoder * OPUS_RESTRICT st, const unsigned char *data, int len, opus_val16 * OPUS_RESTRICT pcm, int frame_size, ec_dec *dec, int accum) { int c, i, N; diff --git a/lib/rbcodec/codecs/libopus/celt/celt_encoder.c b/lib/rbcodec/codecs/libopus/celt/celt_encoder.c index 44cb0850ab..4c5fdaf53c 100644 --- a/lib/rbcodec/codecs/libopus/celt/celt_encoder.c +++ b/lib/rbcodec/codecs/libopus/celt/celt_encoder.c @@ -33,6 +33,10 @@ #define CELT_ENCODER_C +#if defined(CELT_DECODE_ONLY) +#error "CELT_DECODE_ONLY is set in libopus/config.h because SOURCES lists no encoder. Building celt_encoder.c means that is no longer true: remove the define, or the encoder halves of celt/bands.c will have been compiled out." +#endif + #include "cpu_support.h" #include "os_support.h" #include "mdct.h" diff --git a/lib/rbcodec/codecs/libopus/celt/cwrs.c b/lib/rbcodec/codecs/libopus/celt/cwrs.c index 12ff526ad7..6bb8b9be3b 100644 --- a/lib/rbcodec/codecs/libopus/celt/cwrs.c +++ b/lib/rbcodec/codecs/libopus/celt/cwrs.c @@ -461,13 +461,21 @@ void encode_pulses(const int *_y,int _n,int _k,ec_enc *_enc){ } static opus_val32 cwrsi(int _n,int _k,opus_uint32 _i,int *_y){ + const opus_uint32 *rk; + const opus_uint32 *rk1; opus_uint32 p; + opus_uint32 d; int s; int k0; + int kc; opus_int16 val; opus_val32 yy=0; celt_assert(_k>0); celt_assert(_n>1); + /*_k never goes negative, so this cannot match before the pair has been + loaded once.*/ + kc=-1; + rk=rk1=NULL; while(_n>2){ opus_uint32 q; /*Lots of pulses case:*/ @@ -495,12 +503,32 @@ static opus_val32 cwrsi(int _n,int _k,opus_uint32 _i,int *_y){ } /*Lots of dimensions case:*/ else{ - /*Are there any pulses in this dimension at all?*/ - p=CELT_PVQ_U_ROW[_k][_n]; - q=CELT_PVQ_U_ROW[_k+1][_n]; - if(p<=_i&&_i=_n)break; + p=rk[_n]; + q=rk1[_n]; + d=_i-p; + if(d>=q-p)break; + } + /*_n is already decremented for every dimension handled above.*/ + continue; } else{ /*Are the pulses in this dimension negative?*/ @@ -536,7 +564,7 @@ static opus_val32 cwrsi(int _n,int _k,opus_uint32 _i,int *_y){ return yy; } -opus_val32 decode_pulses(int *_y,int _n,int _k,ec_dec *_dec){ +ICODE_ATTR_OPUS opus_val32 decode_pulses(int *_y,int _n,int _k,ec_dec *_dec){ return cwrsi(_n,_k,ec_dec_uint(_dec,CELT_PVQ_V(_n,_k)),_y); } diff --git a/lib/rbcodec/codecs/libopus/celt/ecintrin.h b/lib/rbcodec/codecs/libopus/celt/ecintrin.h index 2263cff6bd..b16721b317 100644 --- a/lib/rbcodec/codecs/libopus/celt/ecintrin.h +++ b/lib/rbcodec/codecs/libopus/celt/ecintrin.h @@ -64,6 +64,9 @@ static __inline int ec_bsr(unsigned long _x){ # include "dsplib.h" # define EC_CLZ0 (31) # define EC_CLZ(_x) (_lnorm(_x)) +#elif defined(OPUS_ARM_ASM_ARMV4_ONLY) && !defined(OPUS_ARM_NO_ILOG_INLINE) +/*ARMv4 has no CLZ instruction, so __builtin_clz compiles to a call to + libgcc's __clzsi2. Leave EC_CLZ undefined and inline EC_ILOG below.*/ #elif __GNUC_PREREQ(3,4) # if INT_MAX>=2147483647 # define EC_CLZ0 ((int)sizeof(unsigned)*CHAR_BIT) @@ -80,6 +83,23 @@ static __inline int ec_bsr(unsigned long _x){ The majority of the time we can never pass it zero. When we need to, it can be special cased.*/ # define EC_ILOG(_x) (EC_CLZ0-EC_CLZ(_x)) +#elif defined(OPUS_ARM_ASM_ARMV4_ONLY) && !defined(OPUS_ARM_NO_ILOG_INLINE) +/*Fifteen branchless instructions, no memory access. The libgcc call it + replaces costs about half again once the call, its table load and the + caller's register saves are counted. Compares against 0x10000, 0x100 and + 0x10 because those are ARM immediates and the equivalent masks are not. + Returns 0 for 0, where the builtin form is undefined.*/ +static __inline__ int ec_ilog_armv4(opus_uint32 _v){ + int ret; + ret=0; + if(_v>=0x10000){_v>>=16;ret=16;} + if(_v>=0x100){_v>>=8;ret+=8;} + if(_v>=0x10){_v>>=4;ret+=4;} + if(_v>=0x4){_v>>=2;ret+=2;} + /*_v is now 0..3, whose ilog is 0, 1, 2, 2.*/ + return ret+(int)(_v&~(_v>>1)); +} +# define EC_ILOG(_x) (ec_ilog_armv4(_x)) #else int ec_ilog(opus_uint32 _v); # define EC_ILOG(_x) (ec_ilog(_x)) diff --git a/lib/rbcodec/codecs/libopus/celt/entcode.c b/lib/rbcodec/codecs/libopus/celt/entcode.c index 461a36dd55..1e3d7914c5 100644 --- a/lib/rbcodec/codecs/libopus/celt/entcode.c +++ b/lib/rbcodec/codecs/libopus/celt/entcode.c @@ -66,7 +66,7 @@ int ec_ilog(opus_uint32 _v){ /* This is a faster version of ec_tell_frac() that takes advantage of the low (1/8 bit) resolution to use just a linear function followed by a lookup to determine the exact transition thresholds. */ -opus_uint32 ec_tell_frac(ec_ctx *_this){ +ICODE_ATTR_OPUS opus_uint32 ec_tell_frac(ec_ctx *_this){ static const unsigned correction[8] = {35733, 38967, 42495, 46340, 50535, 55109, 60097, 65535}; diff --git a/lib/rbcodec/codecs/libopus/celt/entdec.c b/lib/rbcodec/codecs/libopus/celt/entdec.c index 0b3433ed8b..3607f4b0f8 100644 --- a/lib/rbcodec/codecs/libopus/celt/entdec.c +++ b/lib/rbcodec/codecs/libopus/celt/entdec.c @@ -99,7 +99,7 @@ static int ec_read_byte_from_end(ec_dec *_this){ /*Normalizes the contents of val and rng so that rng lies entirely in the high-order symbol.*/ -static void ec_dec_normalize(ec_dec *_this){ +ICODE_ATTR_OPUS static void ec_dec_normalize(ec_dec *_this){ /*If the range is too small, rescale it and input some bits.*/ while(_this->rng<=EC_CODE_BOT){ int sym; @@ -136,21 +136,57 @@ void ec_dec_init(ec_dec *_this,unsigned char *_buf,opus_uint32 _storage){ ec_dec_normalize(_this); } -unsigned ec_decode(ec_dec *_this,unsigned _ft){ +#if defined(OPUS_EC_DECODE_DIV) +/*val/ext for ec_decode() and ec_decode_bin(), without a libgcc call. + While applicable to any CPU with slow division, particularly on ARMv4/v5 + libgcc is built without CLZ, so both ARMv4 and ARMv5E use a shift-subtract + __udivsi3 here, and these two sites make 98.6% of the decoder's calls to it. + The quotient is only used as _ft-EC_MINI(s+1,_ft), so it matters only below + _ft, and every Opus caller has _ft<=32768. Since val>(_j))>=_d){_n-=_d<<(_j);s|=1U<<(_j);} + if(_small){ + if((_n>>9)>=_d)return 0xFFFF; + goto from8; + } + if((_n>>16)>=_d)return 0xFFFF; + EC_DIV_STEP(15) EC_DIV_STEP(14) EC_DIV_STEP(13) EC_DIV_STEP(12) + EC_DIV_STEP(11) EC_DIV_STEP(10) EC_DIV_STEP(9) +from8: + EC_DIV_STEP(8) EC_DIV_STEP(7) EC_DIV_STEP(6) EC_DIV_STEP(5) + EC_DIV_STEP(4) EC_DIV_STEP(3) EC_DIV_STEP(2) EC_DIV_STEP(1) + EC_DIV_STEP(0) +#undef EC_DIV_STEP + return s; +} +# define EC_DEC_DIV(_n,_d,_ft) (ec_dec_div16(_n,_d,(_ft)<=256)) +#else +# define EC_DEC_DIV(_n,_d,_ft) ((unsigned)((_n)/(_d))) +#endif + +ICODE_ATTR_OPUS unsigned ec_decode(ec_dec *_this,unsigned _ft){ + unsigned s; + celt_sig_assert(_ft<=32768); _this->ext=celt_udiv(_this->rng,_ft); - s=(unsigned)(_this->val/_this->ext); + s=EC_DEC_DIV(_this->val,_this->ext,_ft); return _ft-EC_MINI(s+1,_ft); } -unsigned ec_decode_bin(ec_dec *_this,unsigned _bits){ +ICODE_ATTR_OPUS unsigned ec_decode_bin(ec_dec *_this,unsigned _bits){ unsigned s; + celt_sig_assert(_bits<=15); _this->ext=_this->rng>>_bits; - s=(unsigned)(_this->val/_this->ext); + s=EC_DEC_DIV(_this->val,_this->ext,1U<<_bits); return (1U<<_bits)-EC_MINI(s+1U,1U<<_bits); } -void ec_dec_update(ec_dec *_this,unsigned _fl,unsigned _fh,unsigned _ft){ +ICODE_ATTR_OPUS void ec_dec_update(ec_dec *_this,unsigned _fl,unsigned _fh,unsigned _ft){ opus_uint32 s; s=IMUL32(_this->ext,_ft-_fh); _this->val-=s; @@ -159,7 +195,7 @@ void ec_dec_update(ec_dec *_this,unsigned _fl,unsigned _fh,unsigned _ft){ } /*The probability of having a "one" is 1/(1<<_logp).*/ -int ec_dec_bit_logp(ec_dec *_this,unsigned _logp){ +ICODE_ATTR_OPUS int ec_dec_bit_logp(ec_dec *_this,unsigned _logp){ opus_uint32 r; opus_uint32 d; opus_uint32 s; @@ -195,7 +231,7 @@ int ec_dec_icdf(ec_dec *_this,const unsigned char *_icdf,unsigned _ftb){ return ret; } -opus_uint32 ec_dec_uint(ec_dec *_this,opus_uint32 _ft){ +ICODE_ATTR_OPUS opus_uint32 ec_dec_uint(ec_dec *_this,opus_uint32 _ft){ unsigned ft; unsigned s; int ftb; @@ -222,7 +258,7 @@ opus_uint32 ec_dec_uint(ec_dec *_this,opus_uint32 _ft){ } } -opus_uint32 ec_dec_bits(ec_dec *_this,unsigned _bits){ +ICODE_ATTR_OPUS opus_uint32 ec_dec_bits(ec_dec *_this,unsigned _bits){ ec_window window; int available; opus_uint32 ret; diff --git a/lib/rbcodec/codecs/libopus/celt/kiss_fft.c b/lib/rbcodec/codecs/libopus/celt/kiss_fft.c index 83775165d8..fb6b81c1db 100644 --- a/lib/rbcodec/codecs/libopus/celt/kiss_fft.c +++ b/lib/rbcodec/codecs/libopus/celt/kiss_fft.c @@ -40,6 +40,10 @@ #include "os_support.h" #include "mathops.h" #include "stack_alloc.h" +#ifdef OPUS_PFA +#include "pfa.h" +#include "pfa_tables.h" +#endif /* The guts header contains all the multiplication and addition macros that are defined for complex numbers. It also delares the kf_ internal functions. @@ -101,6 +105,7 @@ static void kf_bfly2( } } +#ifndef OVERRIDE_kf_bfly4 static void kf_bfly4( kiss_fft_cpx * Fout, const size_t fstride, @@ -143,21 +148,23 @@ static void kf_bfly4( { Fout = Fout_beg + i*mm; tw3 = tw2 = tw1 = st->twiddles; + /* The first pass twiddles by twiddles[0], which is 1, so it needs no + multiplies at all. Rather than duplicate the body, the loop is + rotated: pass j does its own arithmetic and then twiddles for pass + j+1. In fixed point twiddles[0] is 32767 rather than 32768, so + skipping it also drops a small systematic gain error. */ + scratch[0] = Fout[m]; + scratch[1] = Fout[m2]; + scratch[2] = Fout[m3]; + j = 0; /* m is guaranteed to be a multiple of 4. */ - for (j=0;j= m) + break; + tw1 += fstride; + tw2 += fstride*2; + tw3 += fstride*3; + C_MUL(scratch[0],Fout[m] , *tw1 ); + C_MUL(scratch[1],Fout[m2] , *tw2 ); + C_MUL(scratch[2],Fout[m3] , *tw3 ); } } } } +#endif /* OVERRIDE_kf_bfly4 */ #ifndef RADIX_TWO_ONLY +#ifndef OVERRIDE_kf_bfly3 static void kf_bfly3( kiss_fft_cpx * Fout, const size_t fstride, @@ -200,17 +218,16 @@ static void kf_bfly3( { Fout = Fout_beg + i*mm; tw1=tw2=st->twiddles; + /* twiddles[0] is 1, so the first pass needs no multiplies. The loop is + rotated instead of duplicated: each pass twiddles for the next one. */ + scratch[1] = Fout[m]; + scratch[2] = Fout[m2]; /* For non-custom modes, m is guaranteed to be a multiple of 4. */ k=m; - do { - - C_MUL(scratch[1],Fout[m] , *tw1); - C_MUL(scratch[2],Fout[m2] , *tw2); + for (;;) { C_ADD(scratch[3],scratch[1],scratch[2]); C_SUB(scratch[0],scratch[1],scratch[2]); - tw1 += fstride; - tw2 += fstride*2; Fout[m].r = SUB32_ovflw(Fout->r, HALF_OF(scratch[3].r)); Fout[m].i = SUB32_ovflw(Fout->i, HALF_OF(scratch[3].i)); @@ -226,9 +243,17 @@ static void kf_bfly3( Fout[m].i = ADD32_ovflw(Fout[m].i, scratch[0].r); ++Fout; - } while(--k); + + if (--k == 0) + break; + tw1 += fstride; + tw2 += fstride*2; + C_MUL(scratch[1],Fout[m] , *tw1); + C_MUL(scratch[2],Fout[m2] , *tw2); + } } } +#endif /* OVERRIDE_kf_bfly3 */ #ifndef OVERRIDE_kf_bfly5 @@ -244,20 +269,27 @@ static void kf_bfly5( kiss_fft_cpx *Fout0,*Fout1,*Fout2,*Fout3,*Fout4; int i, u; kiss_fft_cpx scratch[13]; - const kiss_twiddle_cpx *tw; + const kiss_twiddle_cpx *tw1,*tw2,*tw3,*tw4; kiss_twiddle_cpx ya,yb; + /* cos(2*pi/5) + cos(4*pi/5) is exactly -1/2 and cos(2*pi/5) - cos(4*pi/5) + is exactly sqrt(5)/2, so the four cosine products below collapse into a + shift by two plus a single multiply by yc = sqrt(5)/4. The Q15 constants + satisfy the first identity exactly (10126 - 26510 == -16384), so yc is + just (ya.r - yb.r)/2 and no accuracy is given up. */ + opus_val16 yc; kiss_fft_cpx * Fout_beg = Fout; #ifdef FIXED_POINT - ya.r = 10126; + /*ya.r = 10126;*/ /* Folded into yc */ ya.i = -31164; - yb.r = -26510; + /*yb.r = -26510;*/ /* Folded into yc */ yb.i = -19261; + yc = 18318; #else ya = st->twiddles[fstride*m]; yb = st->twiddles[fstride*2*m]; + yc = HALF_OF(ya.r - yb.r); #endif - tw=st->twiddles; for (i=0;itwiddles; + /* twiddles[0] is 1, so the first pass needs no multiplies. The loop is + rotated instead of duplicated: each pass twiddles for the next one. */ + scratch[1] = *Fout1; + scratch[2] = *Fout2; + scratch[3] = *Fout3; + scratch[4] = *Fout4; + u = 0; /* For non-custom modes, m is guaranteed to be a multiple of 4. */ - for ( u=0; ur = ADD32_ovflw(Fout0->r, ADD32_ovflw(scratch[7].r, scratch[8].r)); - Fout0->i = ADD32_ovflw(Fout0->i, ADD32_ovflw(scratch[7].i, scratch[8].i)); + C_ADD( scratch[3],scratch[7],scratch[8]); + C_SUB( scratch[4],scratch[7],scratch[8]); - scratch[5].r = ADD32_ovflw(scratch[0].r, ADD32_ovflw(S_MUL(scratch[7].r,ya.r), S_MUL(scratch[8].r,yb.r))); - scratch[5].i = ADD32_ovflw(scratch[0].i, ADD32_ovflw(S_MUL(scratch[7].i,ya.r), S_MUL(scratch[8].i,yb.r))); + C_ADDTO(*Fout0, scratch[3]); + + /* scratch[0] - (scratch[7]+scratch[8])/4 is the term common to both + output pairs; +/- sqrt(5)/4*(scratch[7]-scratch[8]) separates them. + QUARTER_OF() rounds, which recovers about half the accuracy the + substitution would otherwise cost. */ + scratch[3].r = SUB32_ovflw(scratch[0].r, QUARTER_OF(scratch[3].r)); + scratch[3].i = SUB32_ovflw(scratch[0].i, QUARTER_OF(scratch[3].i)); + scratch[4].r = S_MUL(scratch[4].r,yc); + scratch[4].i = S_MUL(scratch[4].i,yc); + + C_ADD( scratch[5],scratch[3],scratch[4]); + C_SUB( scratch[11],scratch[3],scratch[4]); scratch[6].r = ADD32_ovflw(S_MUL(scratch[10].i,ya.i), S_MUL(scratch[9].i,yb.i)); scratch[6].i = NEG32_ovflw(ADD32_ovflw(S_MUL(scratch[10].r,ya.i), S_MUL(scratch[9].r,yb.i))); @@ -294,8 +342,6 @@ static void kf_bfly5( C_SUB(*Fout1,scratch[5],scratch[6]); C_ADD(*Fout4,scratch[5],scratch[6]); - scratch[11].r = ADD32_ovflw(scratch[0].r, ADD32_ovflw(S_MUL(scratch[7].r,yb.r), S_MUL(scratch[8].r,ya.r))); - scratch[11].i = ADD32_ovflw(scratch[0].i, ADD32_ovflw(S_MUL(scratch[7].i,yb.r), S_MUL(scratch[8].i,ya.r))); scratch[12].r = SUB32_ovflw(S_MUL(scratch[9].i,ya.i), S_MUL(scratch[10].i,yb.i)); scratch[12].i = SUB32_ovflw(S_MUL(scratch[10].r,yb.i), S_MUL(scratch[9].r,ya.i)); @@ -303,6 +349,17 @@ static void kf_bfly5( C_SUB(*Fout3,scratch[11],scratch[12]); ++Fout0;++Fout1;++Fout2;++Fout3;++Fout4; + + if (++u >= m) + break; + tw1 += fstride; + tw2 += fstride*2; + tw3 += fstride*3; + tw4 += fstride*4; + C_MUL(scratch[1] ,*Fout1, *tw1); + C_MUL(scratch[2] ,*Fout2, *tw2); + C_MUL(scratch[3] ,*Fout3, *tw3); + C_MUL(scratch[4] ,*Fout4, *tw4); } } } @@ -518,7 +575,7 @@ void opus_fft_free(const kiss_fft_state *cfg, int arch) #endif /* CUSTOM_MODES */ -void opus_fft_impl(const kiss_fft_state *st,kiss_fft_cpx *fout) +ICODE_ATTR_OPUS_MR void opus_fft_impl(const kiss_fft_state *st,kiss_fft_cpx *fout) { int m2, m; int p; @@ -566,6 +623,60 @@ void opus_fft_impl(const kiss_fft_state *st,kiss_fft_cpx *fout) } } +#ifdef OPUS_PFA +/* kf_bfly4 reads nothing from the state but the twiddle table, and the prime + factor sub-transforms want their own: one 32-entry table serves all four + sizes, read with a stride, exactly as kiss_fft shares a single table + between the four transform lengths. */ +static const kiss_fft_state pfa_st32 = { 0, 0, 0, 0, {0}, 0, pfa_tw32, 0 }; + +ICODE_ATTR_OPUS void opus_pfa_impl(const kiss_fft_cpx *fin, + kiss_fft_cpx *fout, int nfft) +{ + const opus_int16 *brev; + int M = nfft/15; + int n2; + + switch (nfft) + { + case 60: brev = pfa_brev_4; break; + case 120: brev = pfa_brev_8; break; + case 240: brev = pfa_brev_16; break; + default: brev = pfa_brev_32; break; + } + + /* Pass 1: M fifteen-point DFTs. The bit-reversal the radix-4 chain below + expects is folded into this scatter, so pass 2 needs no permutation of + its own. */ + for (n2=0;n2 #include "os_support.h" #include "mathops.h" +#ifdef OPUS_PFA +#include "pfa.h" +#include "pfa_tables.h" + +#if defined(OPUS_ARM_ASM) +#include "arm/mdct_armv4.h" +#include "arm/mdct_armv5e.h" +#endif + +/* The pre-rotation already scatters through a table; for the prime factor + transform it reads this one instead of the bit-reversal, which is why the + gather costs nothing. */ +static const opus_int16 *pfa_gather(int n) +{ + switch (n) + { + case 60: return pfa_in_60; + case 120: return pfa_in_120; + case 240: return pfa_in_240; + default: return pfa_in_480; + } +} + +#ifdef OVERRIDE_MDCT_POSTROT_PFA +/* Where the post-rotation finds X[k], as a byte offset. */ +static const opus_int16 *pfa_postmap(int n) +{ + switch (n) + { + case 60: return pfa_post_60; + case 120: return pfa_post_120; + case 240: return pfa_post_240; + default: return pfa_post_480; + } +} +#endif + +/* Walk p(k) = M*(k mod 15) + (k mod M) by one step. Both residues are + running counters, so the post-rotation needs no index table. */ +#define PFA_ADV_UP(p, k1, k2, M) do { (p) += (M)+1; if (++(k1) == 15) { (k1) = 0; (p) -= 15*(M); } if (++(k2) == (M)) { (k2) = 0; (p) -= (M); } } while (0) +#define PFA_ADV_DN(p, k1, k2, M) do { (p) -= (M)+1; if ((k1)-- == 0) { (k1) = 14; (p) += 15*(M); } if ((k2)-- == 0) { (k2) = (M)-1; (p) += (M); } } while (0) +#endif #include "stack_alloc.h" #if defined(MIPSr1_ASM) @@ -245,6 +287,12 @@ void clt_mdct_backward_c(const mdct_lookup *l, kiss_fft_scalar *in, kiss_fft_sca int i; int N, N2, N4; const kiss_twiddle_scalar *trig; +#ifdef OPUS_PFA + int use_pfa; + kiss_fft_cpx *pfa_out = NULL; + VARDECL(kiss_fft_cpx, pfa_tmp); +#endif + SAVE_STACK; (void) arch; N = l->n; @@ -257,6 +305,18 @@ void clt_mdct_backward_c(const mdct_lookup *l, kiss_fft_scalar *in, kiss_fft_sca N2 = N>>1; N4 = N>>2; +#ifdef OPUS_PFA + /* Pass 1 of the prime factor transform reads fifteen contiguous points + and writes them strided, so it cannot work in place. The input buffer + is exactly the right size and is documented as destroyed here, so for + the usual stride==1 call it costs nothing; a strided call cannot reuse + it, but those are always the short transform, N4 == 60. */ + use_pfa = OPUS_PFA_SIZE(N4) && (stride == 1 || N4 == 60); + ALLOC(pfa_tmp, (use_pfa && stride != 1) ? N4 : ALLOC_NONE, kiss_fft_cpx); + if (use_pfa) + pfa_out = (stride == 1) ? (kiss_fft_cpx *)in : pfa_tmp; +#endif + /* Pre-rotate */ { /* Temp pointers to make it really clear to the compiler what we're doing */ @@ -265,6 +325,13 @@ void clt_mdct_backward_c(const mdct_lookup *l, kiss_fft_scalar *in, kiss_fft_sca kiss_fft_scalar * OPUS_RESTRICT yp = out+(overlap>>1); const kiss_twiddle_scalar * OPUS_RESTRICT t = &trig[0]; const opus_int16 * OPUS_RESTRICT bitrev = l->kfft[shift]->bitrev; +#ifdef OPUS_PFA + if (use_pfa) bitrev = pfa_gather(N4); +#endif +#ifdef OVERRIDE_MDCT_PREROT + mdct_prerot_opt(xp1, xp2, t, bitrev, yp, N4, + 2*stride*(int)sizeof(kiss_fft_scalar)); +#else for(i=0;i>1)), pfa_out, N4); + else +#endif opus_fft_impl(l->kfft[shift], (kiss_fft_cpx*)(out+(overlap>>1))); /* Post-rotate and de-shuffle from both ends of the buffer at once to make @@ -291,6 +364,54 @@ void clt_mdct_backward_c(const mdct_lookup *l, kiss_fft_scalar *in, kiss_fft_sca const kiss_twiddle_scalar *t = &trig[0]; /* Loop to (N4+1)>>1 to handle odd N4. When N4 is odd, the middle pair will be computed twice. */ +#ifdef OPUS_PFA + if (use_pfa) + { + /* Same rotation, but the transform is in Good-Thomas order, so the + two ends are gathered through p(k) rather than read in place. + That is also why this writes a different buffer than it reads. */ + const kiss_fft_scalar *S = (const kiss_fft_scalar *)pfa_out; + int M = N4/15; + int pa = 0, ka = 0, qa = 0; + int kd = (N4-1)%15, qd = (N4-1)&(M-1); + int pd = M*kd + qd; +#ifdef OVERRIDE_MDCT_POSTROT_PFA + mdct_postrot_pfa_opt(S, yp0, yp1, t, pfa_postmap(N4), N4); + (void)pa; (void)ka; (void)qa; (void)pd; (void)kd; (void)qd; (void)M; +#else + for(i=0;i<(N4+1)>>1;i++) + { + kiss_fft_scalar re, im, yr, yi; + kiss_twiddle_scalar t0, t1; + re = S[2*pa+1]; + im = S[2*pa]; + t0 = t[i]; + t1 = t[N4+i]; + yr = ADD32_ovflw(S_MUL(re,t0), S_MUL(im,t1)); + yi = SUB32_ovflw(S_MUL(re,t1), S_MUL(im,t0)); + re = S[2*pd+1]; + im = S[2*pd]; + yp0[0] = yr; + yp1[1] = yi; + + t0 = t[(N4-i-1)]; + t1 = t[(N2-i-1)]; + yr = ADD32_ovflw(S_MUL(re,t0), S_MUL(im,t1)); + yi = SUB32_ovflw(S_MUL(re,t1), S_MUL(im,t0)); + yp1[0] = yr; + yp0[1] = yi; + yp0 += 2; + yp1 -= 2; + PFA_ADV_UP(pa, ka, qa, M); + PFA_ADV_DN(pd, kd, qd, M); + } +#endif + } + else +#endif +#ifdef OVERRIDE_MDCT_POSTROT + mdct_postrot_opt(yp0, yp1, t, N4, (N4+1)>>1); +#else for(i=0;i<(N4+1)>>1;i++) { kiss_fft_scalar re, im, yr, yi; @@ -319,6 +440,7 @@ void clt_mdct_backward_c(const mdct_lookup *l, kiss_fft_scalar *in, kiss_fft_sca yp0 += 2; yp1 -= 2; } +#endif } /* Mirror on both sides for TDAC */ @@ -328,6 +450,9 @@ void clt_mdct_backward_c(const mdct_lookup *l, kiss_fft_scalar *in, kiss_fft_sca const opus_val16 * OPUS_RESTRICT wp1 = window; const opus_val16 * OPUS_RESTRICT wp2 = window+overlap-1; +#ifdef OVERRIDE_MDCT_MIRROR + mdct_mirror_opt(xp1, yp1, wp1, wp2, overlap/2); +#else for(i = 0; i < overlap/2; i++) { kiss_fft_scalar x1, x2; @@ -338,6 +463,8 @@ void clt_mdct_backward_c(const mdct_lookup *l, kiss_fft_scalar *in, kiss_fft_sca wp1++; wp2--; } +#endif } + RESTORE_STACK; } #endif /* OVERRIDE_clt_mdct_backward */ diff --git a/lib/rbcodec/codecs/libopus/celt/pfa.c b/lib/rbcodec/codecs/libopus/celt/pfa.c new file mode 100644 index 0000000000..daca7c82e4 --- /dev/null +++ b/lib/rbcodec/codecs/libopus/celt/pfa.c @@ -0,0 +1,148 @@ +/* Good-Thomas (prime factor) FFT for the CELT backward MDCT. + * + * Copyright (c) 2007-2008 CSIRO + * Copyright (c) 2007-2008 Xiph.Org Foundation + * Copyright (c) 2026 Michael Giacomelli + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the conditions stated in + * celt/kiss_fft.c are met. + * + * Why this exists + * --------------- + * Every FFT length a 48 kHz Opus stream uses is 15 times a power of two: + * 60, 120, 240, 480. 15 and 2^k are co-prime, so the prime factor algorithm + * applies, and its whole point is that co-prime factors need no twiddle + * factors between them -- only an index permutation. + * + * That matters because in the mixed-radix decomposition kiss_fft picks + * (5, 3, 4, 2, 4 for N=480) the inter-stage twiddles are 4,020 of the 5,540 + * real multiplies, 73% of the total. Splitting 480 as 15 x 32 deletes that + * class of work rather than making it cheaper: 2,780 multiplies, half as + * many. Multiplies are 28.4% of ARMv4 decode, so this is the largest single + * item the profile still offers on that CPU. + * + * Structure, for N = 15*M: + * + * gather x[n] -> u[15*n2 + 3*m2 + m1], a table the pre-rotation reads + * in place of the bit-reversal it read before, so it is free + * pass 1 M independent 15-point DFTs, contiguous in, strided M out + * pass 2 15 independent M-point DFTs, contiguous, which is exactly + * what the existing kf_bfly4/kf_bfly2 kernels already do + * scatter X[k] = B[k mod 15][k mod M], absorbed into the post-rotation + * as two running counters, no table + * + * Buffers. Pass 1 cannot be in place (it reads 15 contiguous and writes 15 + * strided), so it needs somewhere to land. clt_mdct_backward already + * destroys its input -- celt_synthesis copies the spectrum out first for + * exactly that reason -- and that buffer is N2 scalars, which is N4 complex, + * exactly the size wanted. So the common stride==1 case costs no memory at + * all. Short blocks arrive with stride>1 and a strided input that cannot be + * reused, but there N4 is only 60, so a 480-byte local covers it. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "kiss_fft.h" +#include "_kiss_fft_guts.h" +#include "mathops.h" +#include "stack_alloc.h" +#include "pfa.h" + +#ifdef OPUS_PFA + +/* The same constants the mixed-radix butterflies use, so the arithmetic + below is the arithmetic that was already shipping, only reassociated. + epi3 is -sin(2pi/3); ya/yb are the radix-5 sines; yc is sqrt(5)/4, which + collapses the four radix-5 cosine products into one multiply because + cos(2pi/5)+cos(4pi/5) is exactly -1/2 and the Q15 constants honour that + identity exactly (10126 - 26510 == -16384). */ +#define PFA_EPI3 (-28378) +#define PFA_YAI (-31164) +#define PFA_YBI (-19261) +#define PFA_YC ( 18318) + +/* PFA_KEEP_C keeps the reference compiled alongside an override so a + harness can score one against the other on target. */ +#if !defined(OVERRIDE_PFA_FFT15) || defined(PFA_KEEP_C) +/* One 15-point DFT, itself Good-Thomas over 3 and 5 so it too carries no + twiddles: five 3-point DFTs, then three 5-point DFTs. 40 real multiplies. + Input is five contiguous triples, laid out by the gather table. Output is + scattered by ostride complex elements; within one 5-point DFT the + destinations run at a stride of 3, which is what lets the kernel walk them + with a single post-indexed store. */ +void pfa_fft15_c(const kiss_fft_cpx *in, kiss_fft_cpx *out, int ostride) +{ + kiss_fft_cpx v[15]; + int g, k1; + + /* Five 3-point DFTs. Results land strided by 5 so each 5-point DFT + below reads a contiguous run. */ + for (g=0;g<5;g++) + { + kiss_fft_cpx x0, s3, s0, xm; + x0 = in[3*g]; + s3.r = ADD32_ovflw(in[3*g+1].r, in[3*g+2].r); + s3.i = ADD32_ovflw(in[3*g+1].i, in[3*g+2].i); + s0.r = SUB32_ovflw(in[3*g+1].r, in[3*g+2].r); + s0.i = SUB32_ovflw(in[3*g+1].i, in[3*g+2].i); + xm.r = SUB32_ovflw(x0.r, HALF_OF(s3.r)); + xm.i = SUB32_ovflw(x0.i, HALF_OF(s3.i)); + s0.r = S_MUL(s0.r, PFA_EPI3); + s0.i = S_MUL(s0.i, PFA_EPI3); + v[g].r = ADD32_ovflw(x0.r, s3.r); + v[g].i = ADD32_ovflw(x0.i, s3.i); + v[5+g].r = SUB32_ovflw(xm.r, s0.i); + v[5+g].i = ADD32_ovflw(xm.i, s0.r); + v[10+g].r = ADD32_ovflw(xm.r, s0.i); + v[10+g].i = SUB32_ovflw(xm.i, s0.r); + } + + /* Three 5-point DFTs. Output k2 of DFT k1 belongs at 15-point index + j = (10*k1 + 6*k2) mod 15; walking j from k1 in steps of 3 visits them + in the order k2 = (k1 + 3*t) mod 5, which is the permutation applied + when storing. */ + for (k1=0;k1<3;k1++) + { + const kiss_fft_cpx *x = v + 5*k1; + kiss_fft_cpx X[5], s7, s8, s9, s10, s3, s4, s5, s11, s6, s12; + int t; + + s7.r = ADD32_ovflw(x[1].r, x[4].r); s7.i = ADD32_ovflw(x[1].i, x[4].i); + s10.r = SUB32_ovflw(x[1].r, x[4].r); s10.i = SUB32_ovflw(x[1].i, x[4].i); + s8.r = ADD32_ovflw(x[2].r, x[3].r); s8.i = ADD32_ovflw(x[2].i, x[3].i); + s9.r = SUB32_ovflw(x[2].r, x[3].r); s9.i = SUB32_ovflw(x[2].i, x[3].i); + + s3.r = ADD32_ovflw(s7.r, s8.r); s3.i = ADD32_ovflw(s7.i, s8.i); + s4.r = SUB32_ovflw(s7.r, s8.r); s4.i = SUB32_ovflw(s7.i, s8.i); + + X[0].r = ADD32_ovflw(x[0].r, s3.r); + X[0].i = ADD32_ovflw(x[0].i, s3.i); + + s3.r = SUB32_ovflw(x[0].r, QUARTER_OF(s3.r)); + s3.i = SUB32_ovflw(x[0].i, QUARTER_OF(s3.i)); + s4.r = S_MUL(s4.r, PFA_YC); + s4.i = S_MUL(s4.i, PFA_YC); + + s5.r = ADD32_ovflw(s3.r, s4.r); s5.i = ADD32_ovflw(s3.i, s4.i); + s11.r = SUB32_ovflw(s3.r, s4.r); s11.i = SUB32_ovflw(s3.i, s4.i); + + s6.r = ADD32_ovflw(S_MUL(s10.i, PFA_YAI), S_MUL(s9.i, PFA_YBI)); + s6.i = NEG32_ovflw(ADD32_ovflw(S_MUL(s10.r, PFA_YAI), S_MUL(s9.r, PFA_YBI))); + X[1].r = SUB32_ovflw(s5.r, s6.r); X[1].i = SUB32_ovflw(s5.i, s6.i); + X[4].r = ADD32_ovflw(s5.r, s6.r); X[4].i = ADD32_ovflw(s5.i, s6.i); + + s12.r = SUB32_ovflw(S_MUL(s9.i, PFA_YAI), S_MUL(s10.i, PFA_YBI)); + s12.i = SUB32_ovflw(S_MUL(s10.r, PFA_YBI), S_MUL(s9.r, PFA_YAI)); + X[2].r = ADD32_ovflw(s11.r, s12.r); X[2].i = ADD32_ovflw(s11.i, s12.i); + X[3].r = SUB32_ovflw(s11.r, s12.r); X[3].i = SUB32_ovflw(s11.i, s12.i); + + for (t=0;t<5;t++) + out[(k1 + 3*t)*ostride] = X[(k1 + 3*t) % 5]; + } +} +#endif /* !OVERRIDE_PFA_FFT15 || PFA_KEEP_C */ + +#endif /* OPUS_PFA */ diff --git a/lib/rbcodec/codecs/libopus/celt/pfa.h b/lib/rbcodec/codecs/libopus/celt/pfa.h new file mode 100644 index 0000000000..bc932d4bb5 --- /dev/null +++ b/lib/rbcodec/codecs/libopus/celt/pfa.h @@ -0,0 +1,43 @@ +/* Good-Thomas (prime factor) FFT for the CELT backward MDCT. + See pfa.c for what it is and why. + + Copyright (c) 2026 Michael Giacomelli + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the conditions stated in + celt/kiss_fft.c are met. +*/ +#ifndef PFA_H +#define PFA_H + +#include "kiss_fft.h" + +#ifdef OPUS_PFA + +void pfa_fft15_c(const kiss_fft_cpx *in, kiss_fft_cpx *out, int ostride); + +#if defined(OPUS_ARM_ASM) && !defined(OPUS_ARM_NO_PFA_ASM) +# include "arm/pfa_arm.h" +#endif + +#ifndef OVERRIDE_PFA_FFT15 +# define PFA_FFT15(in, out, ostride) pfa_fft15_c(in, out, ostride) +#endif + +/* Pass 1 and pass 2. fin holds the gathered spectrum and fout receives the + transform; they must be different buffers, because pass 1 reads fifteen + contiguous points and writes them strided. nfft is 60, 120, 240 or 480. + + The result is left in Good-Thomas order: X[k] sits at + fout[(nfft/15)*(k % 15) + (k % (nfft/15))]. The post-rotation reads it + through that index, which costs two running counters and no table. */ +void opus_pfa_impl(const kiss_fft_cpx *fin, kiss_fft_cpx *fout, int nfft); + +/* True for the lengths opus_pfa_impl handles. Every 48 kHz CELT transform + qualifies; the test exists so a custom mode falls back to kiss_fft. */ +#define OPUS_PFA_SIZE(n) \ + ((n) == 60 || (n) == 120 || (n) == 240 || (n) == 480) + +#endif /* OPUS_PFA */ + +#endif /* PFA_H */ diff --git a/lib/rbcodec/codecs/libopus/celt/pfa_tables.h b/lib/rbcodec/codecs/libopus/celt/pfa_tables.h new file mode 100644 index 0000000000..73f309d370 --- /dev/null +++ b/lib/rbcodec/codecs/libopus/celt/pfa_tables.h @@ -0,0 +1,281 @@ +/* Copyright (c) 2026 Michael Giacomelli + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the conditions stated in + * celt/kiss_fft.c are met. + */ +/* Good-Thomas index tables for the CELT backward MDCT. + + Generated by an offline script. Do not edit. + + N = 15*M with M a power of two, so 15 and M are co-prime and the prime + factor algorithm applies: the 15-point and M-point passes need no twiddle + factors between them at all, which is where the multiply saving comes + from. All that is left is the index algebra, and all of it is either a + table the pre-rotation was already reading or a running counter. +*/ +#ifndef PFA_TABLES_H +#define PFA_TABLES_H + +#include "opus_types.h" +#include "arch.h" + +/* n -> 15*n2 + 3*m2 + m1, for N=60, M=4. */ +static const opus_int16 pfa_in_60[60] ICONST_ATTR = { + 0, 56, 34, 27, 8, 46, 39, 20, 13, 51, 32, 25, + 3, 59, 37, 15, 11, 49, 42, 23, 1, 54, 35, 28, + 6, 47, 40, 18, 14, 52, 30, 26, 4, 57, 38, 16, + 9, 50, 43, 21, 2, 55, 33, 29, 7, 45, 41, 19, + 12, 53, 31, 24, 5, 58, 36, 17, 10, 48, 44, 22, +}; + +/* n -> 15*n2 + 3*m2 + m1, for N=120, M=8. */ +static const opus_int16 pfa_in_120[120] ICONST_ATTR = { + 0, 118, 101, 81, 64, 47, 42, 25, 8, 108, 91, 89, + 69, 52, 35, 15, 13, 116, 96, 79, 62, 57, 40, 23, + 3, 106, 104, 84, 67, 50, 30, 28, 11, 111, 94, 77, + 72, 55, 38, 18, 1, 119, 99, 82, 65, 45, 43, 26, + 6, 109, 92, 87, 70, 53, 33, 16, 14, 114, 97, 80, + 60, 58, 41, 21, 4, 107, 102, 85, 68, 48, 31, 29, + 9, 112, 95, 75, 73, 56, 36, 19, 2, 117, 100, 83, + 63, 46, 44, 24, 7, 110, 90, 88, 71, 51, 34, 17, + 12, 115, 98, 78, 61, 59, 39, 22, 5, 105, 103, 86, + 66, 49, 32, 27, 10, 113, 93, 76, 74, 54, 37, 20, +}; + +/* n -> 15*n2 + 3*m2 + m1, for N=240, M=16. */ +static const opus_int16 pfa_in_240[240] ICONST_ATTR = { + 0, 233, 223, 198, 191, 166, 156, 149, 124, 114, 92, 82, + 72, 50, 40, 15, 8, 238, 213, 206, 181, 171, 164, 139, + 129, 107, 97, 87, 65, 55, 30, 23, 13, 228, 221, 196, + 186, 179, 154, 144, 122, 112, 102, 80, 70, 45, 38, 28, + 3, 236, 211, 201, 194, 169, 159, 137, 127, 117, 95, 85, + 60, 53, 43, 18, 11, 226, 216, 209, 184, 174, 152, 142, + 132, 110, 100, 75, 68, 58, 33, 26, 1, 231, 224, 199, + 189, 167, 157, 147, 125, 115, 90, 83, 73, 48, 41, 16, + 6, 239, 214, 204, 182, 172, 162, 140, 130, 105, 98, 88, + 63, 56, 31, 21, 14, 229, 219, 197, 187, 177, 155, 145, + 120, 113, 103, 78, 71, 46, 36, 29, 4, 234, 212, 202, + 192, 170, 160, 135, 128, 118, 93, 86, 61, 51, 44, 19, + 9, 227, 217, 207, 185, 175, 150, 143, 133, 108, 101, 76, + 66, 59, 34, 24, 2, 232, 222, 200, 190, 165, 158, 148, + 123, 116, 91, 81, 74, 49, 39, 17, 7, 237, 215, 205, + 180, 173, 163, 138, 131, 106, 96, 89, 64, 54, 32, 22, + 12, 230, 220, 195, 188, 178, 153, 146, 121, 111, 104, 79, + 69, 47, 37, 27, 5, 235, 210, 203, 193, 168, 161, 136, + 126, 119, 94, 84, 62, 52, 42, 20, 10, 225, 218, 208, + 183, 176, 151, 141, 134, 109, 99, 77, 67, 57, 35, 25, +}; + +/* n -> 15*n2 + 3*m2 + m1, for N=480, M=32. */ +static const opus_int16 pfa_in_480[480] ICONST_ATTR = { + 0, 229, 458, 204, 433, 167, 393, 142, 371, 117, 331, 80, + 306, 55, 284, 15, 244, 473, 219, 448, 182, 408, 157, 386, + 132, 346, 95, 321, 70, 299, 30, 259, 8, 234, 463, 197, + 423, 172, 401, 147, 361, 110, 336, 85, 314, 45, 274, 23, + 249, 478, 212, 438, 187, 416, 162, 376, 125, 351, 100, 329, + 60, 289, 38, 264, 13, 227, 453, 202, 431, 177, 391, 140, + 366, 115, 344, 75, 304, 53, 279, 28, 242, 468, 217, 446, + 192, 406, 155, 381, 130, 359, 90, 319, 68, 294, 43, 257, + 3, 232, 461, 207, 421, 170, 396, 145, 374, 105, 334, 83, + 309, 58, 272, 18, 247, 476, 222, 436, 185, 411, 160, 389, + 120, 349, 98, 324, 73, 287, 33, 262, 11, 237, 451, 200, + 426, 175, 404, 135, 364, 113, 339, 88, 302, 48, 277, 26, + 252, 466, 215, 441, 190, 419, 150, 379, 128, 354, 103, 317, + 63, 292, 41, 267, 1, 230, 456, 205, 434, 165, 394, 143, + 369, 118, 332, 78, 307, 56, 282, 16, 245, 471, 220, 449, + 180, 409, 158, 384, 133, 347, 93, 322, 71, 297, 31, 260, + 6, 235, 464, 195, 424, 173, 399, 148, 362, 108, 337, 86, + 312, 46, 275, 21, 250, 479, 210, 439, 188, 414, 163, 377, + 123, 352, 101, 327, 61, 290, 36, 265, 14, 225, 454, 203, + 429, 178, 392, 138, 367, 116, 342, 76, 305, 51, 280, 29, + 240, 469, 218, 444, 193, 407, 153, 382, 131, 357, 91, 320, + 66, 295, 44, 255, 4, 233, 459, 208, 422, 168, 397, 146, + 372, 106, 335, 81, 310, 59, 270, 19, 248, 474, 223, 437, + 183, 412, 161, 387, 121, 350, 96, 325, 74, 285, 34, 263, + 9, 238, 452, 198, 427, 176, 402, 136, 365, 111, 340, 89, + 300, 49, 278, 24, 253, 467, 213, 442, 191, 417, 151, 380, + 126, 355, 104, 315, 64, 293, 39, 268, 2, 228, 457, 206, + 432, 166, 395, 141, 370, 119, 330, 79, 308, 54, 283, 17, + 243, 472, 221, 447, 181, 410, 156, 385, 134, 345, 94, 323, + 69, 298, 32, 258, 7, 236, 462, 196, 425, 171, 400, 149, + 360, 109, 338, 84, 313, 47, 273, 22, 251, 477, 211, 440, + 186, 415, 164, 375, 124, 353, 99, 328, 62, 288, 37, 266, + 12, 226, 455, 201, 430, 179, 390, 139, 368, 114, 343, 77, + 303, 52, 281, 27, 241, 470, 216, 445, 194, 405, 154, 383, + 129, 358, 92, 318, 67, 296, 42, 256, 5, 231, 460, 209, + 420, 169, 398, 144, 373, 107, 333, 82, 311, 57, 271, 20, + 246, 475, 224, 435, 184, 413, 159, 388, 122, 348, 97, 326, + 72, 286, 35, 261, 10, 239, 450, 199, 428, 174, 403, 137, + 363, 112, 341, 87, 301, 50, 276, 25, 254, 465, 214, 443, + 189, 418, 152, 378, 127, 356, 102, 316, 65, 291, 40, 269, +}; + +/* Bit-reversal of the M-point sub-FFT, folded into the 15-point + kernel's output scatter so the radix-4 chain sees natural order. */ +static const opus_int16 pfa_brev_4[4] ICONST_ATTR = { + 0, 1, 2, 3, +}; + +static const opus_int16 pfa_brev_8[8] ICONST_ATTR = { + 0, 4, 1, 5, 2, 6, 3, 7, +}; + +static const opus_int16 pfa_brev_16[16] ICONST_ATTR = { + 0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15, +}; + +static const opus_int16 pfa_brev_32[32] ICONST_ATTR = { + 0, 8, 16, 24, 4, 12, 20, 28, 1, 9, 17, 25, 5, 13, 21, 29, + 2, 10, 18, 26, 6, 14, 22, 30, 3, 11, 19, 27, 7, 15, 23, 31, +}; + +/* Where the post-rotation finds X[k]. The transform is left in + Good-Thomas order, X[k] at M*(k mod 15) + (k mod M). Both residues are + running counters, but keeping four of them plus their wrap constants + costs more registers than the kernel has to spare, so the composed index + is tabulated instead, pre-scaled to a byte offset. The post-rotation + walks it forwards for the ascending end and backwards for the descending + one, so a single table serves both. */ +static const opus_int16 pfa_post_60[60] ICONST_ATTR = { + 0, 40, 80, 120, 128, 168, 208, 248, 256, 296, + 336, 376, 384, 424, 464, 24, 32, 72, 112, 152, + 160, 200, 240, 280, 288, 328, 368, 408, 416, 456, + 16, 56, 64, 104, 144, 184, 192, 232, 272, 312, + 320, 360, 400, 440, 448, 8, 48, 88, 96, 136, + 176, 216, 224, 264, 304, 344, 352, 392, 432, 472, +}; + +static const opus_int16 pfa_post_120[120] ICONST_ATTR = { + 0, 72, 144, 216, 288, 360, 432, 504, 512, 584, + 656, 728, 800, 872, 944, 56, 64, 136, 208, 280, + 352, 424, 496, 568, 576, 648, 720, 792, 864, 936, + 48, 120, 128, 200, 272, 344, 416, 488, 560, 632, + 640, 712, 784, 856, 928, 40, 112, 184, 192, 264, + 336, 408, 480, 552, 624, 696, 704, 776, 848, 920, + 32, 104, 176, 248, 256, 328, 400, 472, 544, 616, + 688, 760, 768, 840, 912, 24, 96, 168, 240, 312, + 320, 392, 464, 536, 608, 680, 752, 824, 832, 904, + 16, 88, 160, 232, 304, 376, 384, 456, 528, 600, + 672, 744, 816, 888, 896, 8, 80, 152, 224, 296, + 368, 440, 448, 520, 592, 664, 736, 808, 880, 952, +}; + +static const opus_int16 pfa_post_240[240] ICONST_ATTR = { + 0, 136, 272, 408, 544, 680, 816, 952, 1088, 1224, + 1360, 1496, 1632, 1768, 1904, 120, 128, 264, 400, 536, + 672, 808, 944, 1080, 1216, 1352, 1488, 1624, 1760, 1896, + 112, 248, 256, 392, 528, 664, 800, 936, 1072, 1208, + 1344, 1480, 1616, 1752, 1888, 104, 240, 376, 384, 520, + 656, 792, 928, 1064, 1200, 1336, 1472, 1608, 1744, 1880, + 96, 232, 368, 504, 512, 648, 784, 920, 1056, 1192, + 1328, 1464, 1600, 1736, 1872, 88, 224, 360, 496, 632, + 640, 776, 912, 1048, 1184, 1320, 1456, 1592, 1728, 1864, + 80, 216, 352, 488, 624, 760, 768, 904, 1040, 1176, + 1312, 1448, 1584, 1720, 1856, 72, 208, 344, 480, 616, + 752, 888, 896, 1032, 1168, 1304, 1440, 1576, 1712, 1848, + 64, 200, 336, 472, 608, 744, 880, 1016, 1024, 1160, + 1296, 1432, 1568, 1704, 1840, 56, 192, 328, 464, 600, + 736, 872, 1008, 1144, 1152, 1288, 1424, 1560, 1696, 1832, + 48, 184, 320, 456, 592, 728, 864, 1000, 1136, 1272, + 1280, 1416, 1552, 1688, 1824, 40, 176, 312, 448, 584, + 720, 856, 992, 1128, 1264, 1400, 1408, 1544, 1680, 1816, + 32, 168, 304, 440, 576, 712, 848, 984, 1120, 1256, + 1392, 1528, 1536, 1672, 1808, 24, 160, 296, 432, 568, + 704, 840, 976, 1112, 1248, 1384, 1520, 1656, 1664, 1800, + 16, 152, 288, 424, 560, 696, 832, 968, 1104, 1240, + 1376, 1512, 1648, 1784, 1792, 8, 144, 280, 416, 552, + 688, 824, 960, 1096, 1232, 1368, 1504, 1640, 1776, 1912, +}; + +static const opus_int16 pfa_post_480[480] ICONST_ATTR = { + 0, 264, 528, 792, 1056, 1320, 1584, 1848, 2112, 2376, + 2640, 2904, 3168, 3432, 3696, 120, 384, 648, 912, 1176, + 1440, 1704, 1968, 2232, 2496, 2760, 3024, 3288, 3552, 3816, + 240, 504, 512, 776, 1040, 1304, 1568, 1832, 2096, 2360, + 2624, 2888, 3152, 3416, 3680, 104, 368, 632, 896, 1160, + 1424, 1688, 1952, 2216, 2480, 2744, 3008, 3272, 3536, 3800, + 224, 488, 752, 1016, 1024, 1288, 1552, 1816, 2080, 2344, + 2608, 2872, 3136, 3400, 3664, 88, 352, 616, 880, 1144, + 1408, 1672, 1936, 2200, 2464, 2728, 2992, 3256, 3520, 3784, + 208, 472, 736, 1000, 1264, 1528, 1536, 1800, 2064, 2328, + 2592, 2856, 3120, 3384, 3648, 72, 336, 600, 864, 1128, + 1392, 1656, 1920, 2184, 2448, 2712, 2976, 3240, 3504, 3768, + 192, 456, 720, 984, 1248, 1512, 1776, 2040, 2048, 2312, + 2576, 2840, 3104, 3368, 3632, 56, 320, 584, 848, 1112, + 1376, 1640, 1904, 2168, 2432, 2696, 2960, 3224, 3488, 3752, + 176, 440, 704, 968, 1232, 1496, 1760, 2024, 2288, 2552, + 2560, 2824, 3088, 3352, 3616, 40, 304, 568, 832, 1096, + 1360, 1624, 1888, 2152, 2416, 2680, 2944, 3208, 3472, 3736, + 160, 424, 688, 952, 1216, 1480, 1744, 2008, 2272, 2536, + 2800, 3064, 3072, 3336, 3600, 24, 288, 552, 816, 1080, + 1344, 1608, 1872, 2136, 2400, 2664, 2928, 3192, 3456, 3720, + 144, 408, 672, 936, 1200, 1464, 1728, 1992, 2256, 2520, + 2784, 3048, 3312, 3576, 3584, 8, 272, 536, 800, 1064, + 1328, 1592, 1856, 2120, 2384, 2648, 2912, 3176, 3440, 3704, + 128, 392, 656, 920, 1184, 1448, 1712, 1976, 2240, 2504, + 2768, 3032, 3296, 3560, 3824, 248, 256, 520, 784, 1048, + 1312, 1576, 1840, 2104, 2368, 2632, 2896, 3160, 3424, 3688, + 112, 376, 640, 904, 1168, 1432, 1696, 1960, 2224, 2488, + 2752, 3016, 3280, 3544, 3808, 232, 496, 760, 768, 1032, + 1296, 1560, 1824, 2088, 2352, 2616, 2880, 3144, 3408, 3672, + 96, 360, 624, 888, 1152, 1416, 1680, 1944, 2208, 2472, + 2736, 3000, 3264, 3528, 3792, 216, 480, 744, 1008, 1272, + 1280, 1544, 1808, 2072, 2336, 2600, 2864, 3128, 3392, 3656, + 80, 344, 608, 872, 1136, 1400, 1664, 1928, 2192, 2456, + 2720, 2984, 3248, 3512, 3776, 200, 464, 728, 992, 1256, + 1520, 1784, 1792, 2056, 2320, 2584, 2848, 3112, 3376, 3640, + 64, 328, 592, 856, 1120, 1384, 1648, 1912, 2176, 2440, + 2704, 2968, 3232, 3496, 3760, 184, 448, 712, 976, 1240, + 1504, 1768, 2032, 2296, 2304, 2568, 2832, 3096, 3360, 3624, + 48, 312, 576, 840, 1104, 1368, 1632, 1896, 2160, 2424, + 2688, 2952, 3216, 3480, 3744, 168, 432, 696, 960, 1224, + 1488, 1752, 2016, 2280, 2544, 2808, 2816, 3080, 3344, 3608, + 32, 296, 560, 824, 1088, 1352, 1616, 1880, 2144, 2408, + 2672, 2936, 3200, 3464, 3728, 152, 416, 680, 944, 1208, + 1472, 1736, 2000, 2264, 2528, 2792, 3056, 3320, 3328, 3592, + 16, 280, 544, 808, 1072, 1336, 1600, 1864, 2128, 2392, + 2656, 2920, 3184, 3448, 3712, 136, 400, 664, 928, 1192, + 1456, 1720, 1984, 2248, 2512, 2776, 3040, 3304, 3568, 3832, +}; + +/* Twiddles for the M-point half. One 32-entry table serves every size: + M=16 reads every second entry, M=8 every fourth, M=4 every eighth, which + is exactly the shift mechanism kiss_fft already uses to share one table + between the four transform lengths. */ +static const kiss_twiddle_cpx pfa_tw32[32] ICONST_ATTR = { + { 32767, 0}, + { 32137, -6393}, + { 30273, -12539}, + { 27245, -18204}, + { 23170, -23170}, + { 18204, -27245}, + { 12539, -30273}, + { 6393, -32137}, + { 0, -32767}, + { -6393, -32137}, + {-12539, -30273}, + {-18204, -27245}, + {-23170, -23170}, + {-27245, -18204}, + {-30273, -12539}, + {-32137, -6393}, + {-32767, 0}, + {-32137, 6393}, + {-30273, 12539}, + {-27245, 18204}, + {-23170, 23170}, + {-18204, 27245}, + {-12539, 30273}, + { -6393, 32137}, + { 0, 32767}, + { 6393, 32137}, + { 12539, 30273}, + { 18204, 27245}, + { 23170, 23170}, + { 27245, 18204}, + { 30273, 12539}, + { 32137, 6393}, +}; + +#endif /* PFA_TABLES_H */ diff --git a/lib/rbcodec/codecs/libopus/celt/quant_bands.c b/lib/rbcodec/codecs/libopus/celt/quant_bands.c index 39a221eda5..6b1598107a 100644 --- a/lib/rbcodec/codecs/libopus/celt/quant_bands.c +++ b/lib/rbcodec/codecs/libopus/celt/quant_bands.c @@ -425,7 +425,7 @@ void quant_energy_finalise(const CELTMode *m, int start, int end, opus_val16 *ol } } -void unquant_coarse_energy(const CELTMode *m, int start, int end, opus_val16 *oldEBands, int intra, ec_dec *dec, int C, int LM) +ICODE_ATTR_OPUS void unquant_coarse_energy(const CELTMode *m, int start, int end, opus_val16 *oldEBands, int intra, ec_dec *dec, int C, int LM) { const unsigned char *prob_model = e_prob_model[LM][intra]; int i, c; @@ -490,7 +490,7 @@ void unquant_coarse_energy(const CELTMode *m, int start, int end, opus_val16 *ol } } -void unquant_fine_energy(const CELTMode *m, int start, int end, opus_val16 *oldEBands, int *fine_quant, ec_dec *dec, int C) +ICODE_ATTR_OPUS void unquant_fine_energy(const CELTMode *m, int start, int end, opus_val16 *oldEBands, int *fine_quant, ec_dec *dec, int C) { int i, c; /* Decode finer resolution */ diff --git a/lib/rbcodec/codecs/libopus/celt/rate.c b/lib/rbcodec/codecs/libopus/celt/rate.c index 465e1ba26c..f06f477d12 100644 --- a/lib/rbcodec/codecs/libopus/celt/rate.c +++ b/lib/rbcodec/codecs/libopus/celt/rate.c @@ -529,7 +529,7 @@ static OPUS_INLINE int interp_bits2pulses(const CELTMode *m, int start, int end, return codedBands; } -int clt_compute_allocation(const CELTMode *m, int start, int end, const int *offsets, const int *cap, int alloc_trim, int *intensity, int *dual_stereo, +ICODE_ATTR_OPUS int clt_compute_allocation(const CELTMode *m, int start, int end, const int *offsets, const int *cap, int alloc_trim, int *intensity, int *dual_stereo, opus_int32 total, opus_int32 *balance, int *pulses, int *ebits, int *fine_priority, int C, int LM, ec_ctx *ec, int encode, int prev, int signalBandwidth) { int lo, hi, len, j; diff --git a/lib/rbcodec/codecs/libopus/celt/static_modes_fixed.h b/lib/rbcodec/codecs/libopus/celt/static_modes_fixed.h index d43ca72a7f..0b948fad4c 100644 --- a/lib/rbcodec/codecs/libopus/celt/static_modes_fixed.h +++ b/lib/rbcodec/codecs/libopus/celt/static_modes_fixed.h @@ -101,7 +101,9 @@ static const unsigned char cache_caps50[168] ICONST_ATTR = { #ifndef FFT_TWIDDLES48000_960 #define FFT_TWIDDLES48000_960 -static const kiss_twiddle_cpx fft_twiddles48000_960[480] ICONST_ATTR = { +/* Read only by the mixed-radix butterflies, which the prime factor + transform replaces; ICONST_ATTR_OPUS_MR drops it out of IRAM there. */ +static const kiss_twiddle_cpx fft_twiddles48000_960[480] ICONST_ATTR_OPUS_MR = { {32767, 0}, {32766, -429}, {32757, -858}, {32743, -1287}, {32724, -1715}, {32698, -2143}, diff --git a/lib/rbcodec/codecs/libopus/celt/vq.c b/lib/rbcodec/codecs/libopus/celt/vq.c index a6b5552d69..1c2aa776bb 100644 --- a/lib/rbcodec/codecs/libopus/celt/vq.c +++ b/lib/rbcodec/codecs/libopus/celt/vq.c @@ -38,9 +38,12 @@ #include "bands.h" #include "rate.h" #include "pitch.h" +#if defined(OPUS_ARM_ASM) +#include "arm/vq_arm.h" +#endif #ifndef OVERRIDE_vq_exp_rotation1 -static void exp_rotation1(celt_norm *X, int len, int stride, opus_val16 c, opus_val16 s) +ICODE_ATTR_OPUS static void exp_rotation1(celt_norm *X, int len, int stride, opus_val16 c, opus_val16 s) { int i; opus_val16 ms; @@ -67,7 +70,7 @@ static void exp_rotation1(celt_norm *X, int len, int stride, opus_val16 c, opus_ } #endif /* OVERRIDE_vq_exp_rotation1 */ -void exp_rotation(celt_norm *X, int len, int dir, int stride, int K, int spread) +ICODE_ATTR_OPUS void exp_rotation(celt_norm *X, int len, int dir, int stride, int K, int spread) { static const int SPREAD_FACTOR[3]={15,10,5}; int i; @@ -114,7 +117,7 @@ void exp_rotation(celt_norm *X, int len, int dir, int stride, int K, int spread) /** Takes the pitch vector and the decoded residual vector, computes the gain that will give ||p+g*y||=1 and mixes the residual with the pitch. */ -static void normalise_residual(int * OPUS_RESTRICT iy, celt_norm * OPUS_RESTRICT X, +ICODE_ATTR_OPUS static void normalise_residual(int * OPUS_RESTRICT iy, celt_norm * OPUS_RESTRICT X, int N, opus_val32 Ryy, opus_val16 gain) { int i; @@ -130,10 +133,15 @@ static void normalise_residual(int * OPUS_RESTRICT iy, celt_norm * OPUS_RESTRICT t = VSHR32(Ryy, 2*(k-7)); g = MULT16_16_P15(celt_rsqrt_norm(t),gain); +#if defined(FIXED_POINT) && defined(OVERRIDE_NORMRES_SCALE) + (void)i; + NORMRES_SCALE(X, iy, N, g, k+1); +#else i=0; do X[i] = EXTRACT16(PSHR32(MULT16_16(g, iy[i]), k+1)); while (++i < N); +#endif } static unsigned extract_collapse_mask(int *iy, int N, int B) @@ -356,7 +364,7 @@ unsigned alg_quant(celt_norm *X, int N, int K, int spread, int B, ec_enc *enc, /** Decode pulse vector and combine the result with the pitch vector to produce the final normalised signal in the current band. */ -unsigned alg_unquant(celt_norm *X, int N, int K, int spread, int B, +ICODE_ATTR_OPUS unsigned alg_unquant(celt_norm *X, int N, int K, int spread, int B, ec_dec *dec, opus_val16 gain) { opus_val32 Ryy; @@ -376,7 +384,7 @@ unsigned alg_unquant(celt_norm *X, int N, int K, int spread, int B, } #ifndef OVERRIDE_renormalise_vector -void renormalise_vector(celt_norm *X, int N, opus_val16 gain, int arch) +ICODE_ATTR_OPUS void renormalise_vector(celt_norm *X, int N, opus_val16 gain, int arch) { int i; #ifdef FIXED_POINT diff --git a/lib/rbcodec/codecs/libopus/config.h b/lib/rbcodec/codecs/libopus/config.h index f9fdfb0ec8..43cd6019bf 100644 --- a/lib/rbcodec/codecs/libopus/config.h +++ b/lib/rbcodec/codecs/libopus/config.h @@ -1,13 +1,49 @@ #ifndef CONFIG_H #define CONFIG_H +/* rbcodecconfig.h pulls in the firmware config.h, which is assembly-safe and + is what the kernels under celt/arm come here for. Everything below it is + C, so the .S files skip it. */ #include "rbcodecconfig.h" +#ifndef __ASSEMBLER__ #include "codeclib.h" #include "ogg/ogg.h" +#endif /* general stuff */ #define OPUS_BUILD +/* Rockbox decodes Opus and never encodes it: neither celt_encoder.c nor + opus_encoder.c is listed in SOURCES. Saying so lets gcc fold away the + encoder halves of the routines celt/bands.c shares between the two + directions, which is worth 2,976 bytes of code and, because those branches + were holding values live across quant_partition's recursive calls, a + measurable amount of its stack spill. celt_encoder.c carries an #error + against this define, so adding an encoder back fails the build loudly + rather than miscompiling. */ +#define CELT_DECODE_ONLY + +/* Code in IRAM. PP5022 and PP5024 give a codec an 80 KB IRAM window and Opus + already spends 39 KB of it on tables and .ibss; the hot decode path is + about 34 KB of code, so it fits with room over. Every other PP has a 48 KB + window, which the tables would leave too little of, and the AS3525 codec + link defines no IRAM region at all because the whole codec already runs + from IRAM there. libcook splits the same way, for the same reason. + Measured on a Sansa e200v1: 48.96 MHz to 42.92, a 12.3% cut that the cycle + model cannot see at all, because it does not model an instruction cache. + Define OPUS_NO_ICODE to build without it and compare. */ +#if (CONFIG_CPU == PP5022 || CONFIG_CPU == PP5024) && !defined(OPUS_NO_ICODE) +#define OPUS_ARM_ICODE /* also read by the .S kernels */ +#endif + +#ifndef __ASSEMBLER__ +#ifdef OPUS_ARM_ICODE +#define ICODE_ATTR_OPUS ICODE_ATTR +#else +#define ICODE_ATTR_OPUS +#endif +#endif + /* alloc stuff */ #define VAR_ARRAYS #define NORM_ALIASING_HACK @@ -42,11 +78,52 @@ #if defined(CPU_ARM) #define OPUS_ARM_ASM +/* Upstream's OPUS_ARM_INLINE_ASM and OPUS_ARM_INLINE_EDSP, renamed: upstream + layers EDSP on top of ASM, but here exactly one is defined per core. */ #if ARM_ARCH == 4 -#define OPUS_ARM_INLINE_ASM +#define OPUS_ARM_ASM_ARMV4_ONLY #elif ARM_ARCH > 4 -#define OPUS_ARM_INLINE_EDSP +#define OPUS_ARM_ASM_ARMV5E_AND_LATER +#if (ARCH_PROFILE == ARM_PROFILE_MICRO) +#define OPUS_ARM_NO_FFT_ASM +#define OPUS_ARM_NO_MDCT_ASM +#define OPUS_ARM_NO_PITCH_ASM +#define OPUS_NO_PFA +#define OPUS_ARM_NO_COMB_ASM +#define OPUS_ARM_NO_BANDS_ASM +#define OPUS_ARM_NO_SILK_ASM #endif +#endif /* ARM_ARCH */ + +/*optimization no hardware division support*/ +#if !defined(ARM_HAVE_HW_DIV) +#define OPUS_EC_DECODE_DIV +#endif + +/* Exact lookup table for bitexact_log2tan. Saves about 0.25 MHz if the + tables can live in IRAM, which only the ARMv4 PP5022/PP5024 have room for, + and nothing otherwise, so it is only built there. */ +#if CONFIG_CPU == PP5022 || CONFIG_CPU == PP5024 +#define OPUS_LOG2TAN_TABLE +#endif +#endif + +/* Good-Thomas FFT for the backward MDCT. Every 48 kHz CELT transform length + is 15 times a power of two, so the prime factor algorithm applies and the + inter-stage twiddles -- 73% of the FFT multiplies -- disappear. Define + OPUS_NO_PFA to fall back to the mixed-radix chain. */ +#ifndef OPUS_NO_PFA +#define OPUS_PFA +#endif + +/* Mixed-radix leftovers: built, but not worth IRAM once the prime + factor transform makes them unreachable. */ +#ifdef OPUS_PFA +#define ICODE_ATTR_OPUS_MR +#define ICONST_ATTR_OPUS_MR +#else +#define ICODE_ATTR_OPUS_MR ICODE_ATTR_OPUS +#define ICONST_ATTR_OPUS_MR ICONST_ATTR #endif #if defined(CPU_COLDFIRE) @@ -54,4 +131,3 @@ #endif #endif /* CONFIG_H */ - diff --git a/lib/rbcodec/codecs/libopus/silk/SigProc_FIX.h b/lib/rbcodec/codecs/libopus/silk/SigProc_FIX.h index f9ae326326..a6685a99a5 100644 --- a/lib/rbcodec/codecs/libopus/silk/SigProc_FIX.h +++ b/lib/rbcodec/codecs/libopus/silk/SigProc_FIX.h @@ -621,11 +621,11 @@ static OPUS_INLINE opus_int64 silk_max_64(opus_int64 a, opus_int64 b) #include "MacroCount.h" #include "MacroDebug.h" -#ifdef OPUS_ARM_INLINE_ASM +#ifdef OPUS_ARM_ASM_ARMV4_ONLY #include "arm/SigProc_FIX_armv4.h" #endif -#ifdef OPUS_ARM_INLINE_EDSP +#ifdef OPUS_ARM_ASM_ARMV5E_AND_LATER #include "arm/SigProc_FIX_armv5e.h" #endif diff --git a/lib/rbcodec/codecs/libopus/silk/arm/gen_resampler_armv4.py b/lib/rbcodec/codecs/libopus/silk/arm/gen_resampler_armv4.py new file mode 100644 index 0000000000..a39d5d0ed0 --- /dev/null +++ b/lib/rbcodec/codecs/libopus/silk/arm/gen_resampler_armv4.py @@ -0,0 +1,274 @@ +#!/usr/bin/env python3 +"""Generate silk/arm/resampler_armv4_asm.S. + +The FIR half of silk_resampler_private_IIR_FIR at 8, 12 and 16 kHz uses two +or three fixed phase filters in a fixed cycle (see the C), so every +coefficient is a compile-time constant. On ARM7TDMI a constant multiply is +cheaper as shifts and adds than as mla: each add with a shifted operand is +one cycle, where mla is 3-4 cycles plus a load of the coefficient. + +The kernel is sample-major: each input sample is loaded once per cycle and +added into the two or three accumulators it feeds, so a partial product +such as x*(1+2^a) can be shared between the coefficients one sample meets. +The search below finds, per sample, the cheapest set of up to two such +temporaries and the fewest shifted terms per coefficient. + + python3 gen_resampler_armv4.py > resampler_armv4_asm.S +""" +import itertools +import random +import sys +from functools import lru_cache + +T = [[189, -600, 617, 30567], [117, -159, -1070, 29704], + [52, 221, -2392, 28276], [-4, 529, -3350, 26341], + [-48, 758, -3956, 23973], [-80, 905, -4235, 21254], + [-99, 972, -4222, 18278], [-107, 967, -3957, 15143], + [-103, 896, -3487, 11950], [-91, 773, -2865, 8798], + [-71, 611, -2143, 5784], [-46, 425, -1375, 2996]] +# The eight taps of phase p, in sample order, as the C applies them. +P = {p: T[p] + T[11 - p][::-1] for p in range(12)} + +LIM = 1 << 17 + + +def csd(c): + """Canonical signed digits of c as (sign, shift).""" + d, e = [], 0 + while c: + if c & 1: + r = 2 - (c & 3) + d.append((r, e)) + c -= r + c >>= 1 + e += 1 + return d + + +@lru_cache(None) +def singles(V): + s = {} + for v in V: + k = 0 + while (v << k) < LIM: + for sg in (1, -1): + s.setdefault(sg * (v << k), (sg, v, k)) + k += 1 + return s + + +@lru_cache(None) +def doubles(V): + s1 = singles(V) + d = {} + for a, b in itertools.product(s1.items(), repeat=2): + d.setdefault(a[0] + b[0], (a[1], b[1])) + return d + + +@lru_cache(None) +def terms(c, V): + """Fewest (sign, v, shift) terms, v in V, summing to c (None if > 4).""" + s1, s2 = singles(V), doubles(V) + if c in s1: + return [s1[c]] + if c in s2: + return list(s2[c]) + for a, ta in s1.items(): + if c - a in s2: + return [ta] + list(s2[c - a]) + for a, ta in s2.items(): + if c - a in s2: + return list(ta) + list(s2[c - a]) + return None + + +def plain(c): + return [(sg, 1, k) for sg, k in csd(c)] + + +TEMPS = sorted({(1 << a) + 1 for a in range(1, 15)} | + {(1 << a) - 1 for a in range(2, 15)}) + + +def program(cs): + """(temps, [terms per coefficient]) with the fewest instructions.""" + best = (sum(len(csd(c)) for c in cs), (), [plain(c) for c in cs]) + for n in (1, 2): + for ts in itertools.combinations(TEMPS, n): + V = (1,) + ts + tl = [] + cost = n + for c in cs: + t = terms(c, V) + if t is None or len(t) > len(csd(c)): + t = plain(c) + tl.append(t) + cost += len(t) + if cost >= best[0]: + break + else: + best = (cost, ts, tl) + return best + + +# Rates: step, phases in cycle order, window offset of each output, samples +# advanced per cycle. +RATES = [ + ("16k", 43691, [0, 8, 4], [0, 0, 1], 2), + ("8k", 21846, [0, 4, 8], [0, 0, 0], 1), + ("12k", 32768, [0, 6], [0, 0], 1), +] + +ACC = ["r3", "r4", "r5"] +XR = ["r6", "r7"] +TR = ["r8", "r9"] + + +def gen_rate(name, step, phases, offs, adv, out): + nsamp = max(offs) + 8 + # coefficients each sample meets, per accumulator + need = [] + for j in range(nsamp): + cs = [] + for o, (p, off) in enumerate(zip(phases, offs)): + i = j - off + if 0 <= i < 8: + cs.append((o, P[p][i])) + need.append(cs) + # Process x1 .. x(n-1), then x0, whose load post-increments the pointer. + order = list(range(1, nsamp)) + [0] + progs = {} + total = 0 + for j in order: + cost, ts, tl = program(tuple(c for _, c in need[j])) + progs[j] = (ts, tl) + total += cost + # Check every program arithmetically before emitting it. + for j in order: + ts, tl = progs[j] + for x in (1, -1, 32767, -32768, random.randint(-32768, 32767)): + val = {1: x} + for t in ts: + val[t] = x * t + for (o, c), tt in zip(need[j], tl): + assert sum(sg * (val[v] << k) for sg, v, k in tt) == c * x, \ + (name, j, c, tt) + + w = out.write + fn = "silk_IIR_FIR_%s_armv4" % name + w("\n/* %s: phases %s, %d samples a cycle, advancing %d. %d shifted" + " adds a\n cycle, %.1f an output. */\n" + % (name, "/".join(map(str, phases)), nsamp, adv, total, + total / len(phases))) + w(" .global %s\n .type %s, %%function\n%s:\n" % (fn, fn, fn)) + w(" push {r4-r11, lr}\n") + w(" mov r10, #0x7f00\n orr r10, r10, #0xff @ 0x7fff, for the clamp\n") + w(" mov r11, #0x4000 @ the rounding bias\n") + w(" ldrsh %s, [r1, #2] @ x1\n" % XR[0]) + w(".L%s_loop:\n" % name) + started = set() + for n, j in enumerate(order): + xr = XR[n % 2] + ts, tl = progs[j] + body = [] + treg = {1: xr} + for t, r in zip(ts, TR): + treg[t] = r + if (t - 1) & (t - 2) == 0: # 2^a + 1 + a = (t - 1).bit_length() - 1 + body.append("add %s, %s, %s, lsl #%d" % (r, xr, xr, a)) + else: # 2^a - 1 + a = (t + 1).bit_length() - 1 + assert (1 << a) - 1 == t + body.append("rsb %s, %s, %s, lsl #%d" % (r, xr, xr, a)) + for (o, c), tt in zip(need[j], tl): + acc = ACC[o] + first = len(body) + for sg, v, k in tt: + sh = (", lsl #%d" % k) if k else "" + base = acc if o in started else "r11" + started.add(o) + body.append("%s %s, %s, %s%s" % ("add" if sg > 0 else "sub", + acc, base, treg[v], sh)) + body[first] += " " * max(1, 34 - len(body[first])) + \ + "@ x%d * %d" % (j, c) + # The next sample's load goes after this program's first instruction, + # into the register the sample before this one has finished with. + if n + 1 < len(order): + nj = order[n + 1] + if nj == 0: + ld = "ldrsh %s, [r1], #%d" % (XR[(n + 1) % 2], 2 * adv) + ld += " " * max(1, 34 - len(ld)) + "@ x0, and advance" + else: + ld = "ldrsh %s, [r1, #%d]" % (XR[(n + 1) % 2], 2 * nj) + ld += " " * max(1, 34 - len(ld)) + "@ x%d" % nj + body.insert(1, ld) + for b in body: + w(" " + b + "\n") + # Clamp and store; the next cycle's x1 loads under the first clamp. + for o in range(len(phases)): + acc, r = ACC[o], TR[o % 2] + w(" teq %s, %s, lsl #1 @ out of 16 bits?\n" % (acc, acc)) + if o == 0: + w(" ldrsh %s, [r1, #2] @ next x1\n" % XR[0]) + w(" mov %s, %s, asr #15\n" % (r, acc)) + w(" eormi %s, r10, %s, asr #31\n" % (r, acc)) + w(" strh %s, [r0], #2\n" % r) + w(" subs r2, r2, #1\n") + w(" bne .L%s_loop\n" % name) + w(" pop {r4-r11, pc}\n") + w(" .size %s, .-%s\n" % (fn, fn)) + return total + + +HEAD = """/* Copyright (c) 2026 Michael Giacomelli + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the conditions stated in + * silk/resampler_private_IIR_FIR.c are met. + */ + +/* GENERATED by silk/arm/gen_resampler_armv4.py -- edit that, not this. + * + * ARMv4 FIR interpolation for silk_resampler_private_IIR_FIR at the three + * SILK rates. Each step visits two or three fixed phase filters in a fixed + * cycle, so every coefficient is a constant, and on ARM7TDMI a constant + * multiply is cheaper as adds of shifted operands (one cycle each) than as + * mla (three or four, plus loading the coefficient). + * + * Sample-major: each input sample is loaded once a cycle and added into the + * two or three accumulators it feeds, sharing a partial product x*(2^a+-1) + * between them where that saves an add. The rounding bias rides in on + * each accumulator's first add. Every sum is exact in 32 bits: the largest + * phase has sum |c| = 50,045, so |sum| < 2^31 for 16-bit input. + * + * opus_int16 *silk_IIR_FIR__armv4(opus_int16 *out, + * const opus_int16 *buf, int cycles) + * + * Runs `cycles` whole cycles (cycles > 0) and returns the advanced out. + * r0 out, r1 buf, r2 cycles, r3-r5 accumulators, r6/r7 samples, r8/r9 + * partial products, r10 0x7fff, r11 the rounding bias. + */ + +#if defined(__thumb__) || defined(__thumb2__) +#error "resampler_armv4_asm.S must be assembled in ARM mode" +#endif + + .text + .align 2 +""" + + +def main(): + random.seed(1) + out = sys.stdout + out.write(HEAD) + tot = {} + for r in RATES: + tot[r[0]] = gen_rate(*r, out=out) + sys.stderr.write("adds per cycle: %s\n" % tot) + + +if __name__ == "__main__": + main() diff --git a/lib/rbcodec/codecs/libopus/silk/arm/gen_resampler_armv5e.py b/lib/rbcodec/codecs/libopus/silk/arm/gen_resampler_armv5e.py new file mode 100644 index 0000000000..ae29853b90 --- /dev/null +++ b/lib/rbcodec/codecs/libopus/silk/arm/gen_resampler_armv5e.py @@ -0,0 +1,224 @@ +#!/usr/bin/env python3 +"""Generate silk/arm/resampler_armv5e_asm.S. + +The FIR half of silk_resampler_private_IIR_FIR at 8, 12 and 16 kHz, for +ARMv5E and later. Unlike ARMv4 (gen_resampler_armv4.py, beside this one), +the multiplier is the cheap path here: smla is a 16x16 +multiply-accumulate that issues in one cycle and picks either half of each +operand, so two samples arrive in one word, two coefficients in another, and +nothing is unpacked. + + python3 gen_resampler_armv5e.py > resampler_armv5e_asm.S +""" +import sys + +T = [[189, -600, 617, 30567], [117, -159, -1070, 29704], + [52, 221, -2392, 28276], [-4, 529, -3350, 26341], + [-48, 758, -3956, 23973], [-80, 905, -4235, 21254], + [-99, 972, -4222, 18278], [-107, 967, -3957, 15143], + [-103, 896, -3487, 11950], [-91, 773, -2865, 8798], + [-71, 611, -2143, 5784], [-46, 425, -1375, 2996]] +P = {p: T[p] + T[11 - p][::-1] for p in range(12)} + +# Rates: name, outputs per loop iteration as (phase, first sample), and the +# words the window advances per iteration. The buffer is word aligned, so +# sample s is half s&1 of word s>>1. 8 and 12 kHz advance one sample a +# cycle, so an iteration is two cycles and advances one word. +RATES = [ + ("16k", [(0, 0), (8, 0), (4, 1)], 1), + ("8k", [(0, 0), (4, 0), (8, 0), (0, 1), (4, 1), (8, 1)], 1), + ("12k", [(0, 0), (6, 0), (0, 1), (6, 1)], 1), +] + +W = ["r5", "r6", "r7", "r8", "r9"] # sample words W0..W4 +CP = ["r10", "r12"] # coefficient words, first chain +CQ = ["r11", "lr"] # and second + + +def half(h): + return "t" if h else "b" + + +def coef_words(p): + """The four words of phase p's taps, low half the even tap.""" + c = P[p] + return [((c[2 * i + 1] & 0xffff) << 16) | (c[2 * i] & 0xffff) + for i in range(4)] + + +class Pool: + def __init__(self, name): + self.name, self.words = name, [] + + def label(self, w): + if w not in self.words: + self.words.append(w) + return ".L%s_k%d" % (self.name, self.words.index(w)) + + +def mac(acc, tap, first, off, cw, bias): + """smla/smul for tap `tap` of an output whose window starts at off.""" + s = off + tap + x, y = half(s & 1), half(tap & 1) + if first and not bias: + return "smul%s%s %s, %s, %s" % (x, y, acc, W[s >> 1], cw) + return "smla%s%s %s, %s, %s, %s" % (x, y, acc, W[s >> 1], cw, acc) + + +def chains(pool, a, b, first_loads_done): + """Two interleaved multiply-accumulate chains. + + a and b are (acc, [(tap, off, word)], bias): each word covers two taps. + Returns the instructions; the first word of each chain is expected to + be loaded already into CP[0] and CQ[0]. + """ + out = [] + (acc_a, wa, bias_a), (acc_b, wb, bias_b) = a, b + n = max(len(wa), len(wb)) + for w in range(n): + ra, rb = CP[w % 2], CQ[w % 2] + na, nb = CP[(w + 1) % 2], CQ[(w + 1) % 2] + if w < len(wa): + tap, off, _ = wa[w] + out.append(mac(acc_a, tap, w == 0, off, ra, bias_a)) + if w < len(wb): + tap, off, _ = wb[w] + out.append(mac(acc_b, tap, w == 0, off, rb, bias_b)) + if w + 1 < len(wa): + out.append("ldr %s, %s" % (na, pool.label(wa[w + 1][2]))) + if w < len(wa): + tap, off, _ = wa[w] + out.append(mac(acc_a, tap + 1, False, off, ra, bias_a)) + if w + 1 < len(wb): + out.append("ldr %s, %s" % (nb, pool.label(wb[w + 1][2]))) + if w < len(wb): + tap, off, _ = wb[w] + out.append(mac(acc_b, tap + 1, False, off, rb, bias_b)) + return out + + +def gen_rate(name, outs, adv, f): + pool = Pool(name) + fn = "silk_IIR_FIR_%s_armv5e" % name + w = f.write + w("\n/* %s: %d outputs an iteration, phases %s. */\n" + % (name, len(outs), "/".join(str(p) for p, _ in outs))) + w(" .global %s\n .type %s, %%function\n%s:\n" % (fn, fn, fn)) + w(" push {r4-r11, lr}\n") + w(".L%s_loop:\n" % name) + + # Group outputs in pairs, two chains each; an odd one out is split into + # its first and last four taps and the halves summed. + groups = [] + i = 0 + while i < len(outs): + if i + 1 < len(outs): + groups.append(("pair", outs[i], outs[i + 1])) + i += 2 + else: + groups.append(("split", outs[i])) + i += 1 + + def spec(p, off): + return [(2 * k, off, cw) for k, cw in enumerate(coef_words(p))] + + body = [] + body.append("ldr %s, [r1], #%d" % (W[0], 4 * adv)) + body.append("ldmia r1, {%s-%s}" % (W[1], W[4])) + loaded = False + for g in groups: + if g[0] == "pair": + (pa, oa), (pb, ob) = g[1], g[2] + a = ("r3", spec(pa, oa), True) + b = ("r4", spec(pb, ob), True) + desc = ["phase %d at x%d" % (pa, oa), "phase %d at x%d" % (pb, ob)] + else: + p, o = g[1] + s = spec(p, o) + a = ("r3", s[:2], True) + b = ("r4", s[2:], False) + desc = ["phase %d at x%d, taps 0-3" % (p, o), "taps 4-7"] + pre = ["ldr %s, %s" % (CP[0], pool.label(a[1][0][2])), + "ldr %s, %s" % (CQ[0], pool.label(b[1][0][2])), + "mov r3, #0x4000 @ rounding bias"] + if b[2]: + pre.append("mov r4, #0x4000") + pre[0] += " " * max(1, 36 - len(pre[0])) + "@ " + desc[0] + pre[1] += " " * max(1, 36 - len(pre[1])) + "@ " + desc[1] + body += pre + body += chains(pool, a, b, loaded) + if g[0] == "pair": + body += ["qadd r3, r3, r3 @ SAT16(x >> 15)", + "qadd r4, r4, r4 @ in the top half", + "mov r3, r3, asr #16", + "mov r4, r4, asr #16", + "strh r3, [r0], #2", + "strh r4, [r0], #2"] + else: + body += ["add r3, r3, r4", + "qadd r3, r3, r3 @ SAT16(x >> 15)", + "mov r3, r3, asr #16", + "strh r3, [r0], #2"] + body += ["subs r2, r2, #1", "bne .L%s_loop" % name, + "pop {r4-r11, pc}"] + for b in body: + w(" " + b + "\n") + w(" .align 2\n") + for k, v in enumerate(pool.words): + lo, hi = v & 0xffff, v >> 16 + sg = lambda h: h - 0x10000 if h & 0x8000 else h + w(".L%s_k%d: .word 0x%08x @ %d, %d\n" + % (name, k, v, sg(lo), sg(hi))) + w(" .size %s, .-%s\n" % (fn, fn)) + + +HEAD = """/* Copyright (c) 2026 Michael Giacomelli + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the conditions stated in + * silk/resampler_private_IIR_FIR.c are met. + */ + +/* GENERATED by silk/arm/gen_resampler_armv5e.py -- edit that, not this. + * + * ARMv5E FIR interpolation for silk_resampler_private_IIR_FIR at the three + * SILK rates. Each step visits two or three fixed phase filters in a fixed + * cycle (see the C), so the coefficients are constants, packed two to a word + * in each function's literal pool. The buffer is word aligned, so one load + * brings two samples and smla takes whichever halves a tap needs: + * a window starting on an odd sample costs nothing extra. + * + * Outputs are computed in pairs, as two interleaved multiply-accumulate + * chains, so no smla waits on the one before it. An odd output out splits + * its eight taps across the two chains and sums them. The rounding bias + * starts each accumulator, and qadd doubles with saturation, so the top half + * is exactly SAT16((sum + 2^14) >> 15). + * + * opus_int16 *silk_IIR_FIR__armv5e(opus_int16 *out, + * const opus_int16 *buf, int iters) + * + * buf must be word aligned. Runs `iters` whole iterations (iters > 0) -- + * one cycle at 16 kHz, two at 8 and 12 kHz -- and returns the advanced out. + * Each iteration reads the ten samples from buf and advances it one word. + * r0 out, r1 buf, r2 iters, r3/r4 accumulators, r5-r9 sample words, + * r10/r12 and r11/lr coefficient words for the two chains. + */ + +#if defined(__thumb__) || defined(__thumb2__) +#error "resampler_armv5e_asm.S must be assembled in ARM mode" +#endif + + .text + .align 2 +""" + + +def main(): + f = sys.stdout + f.write(HEAD) + for r in RATES: + gen_rate(*r, f=f) + + +if __name__ == "__main__": + main() diff --git a/lib/rbcodec/codecs/libopus/silk/arm/resampler_armv4_asm.S b/lib/rbcodec/codecs/libopus/silk/arm/resampler_armv4_asm.S new file mode 100644 index 0000000000..df7983eeb6 --- /dev/null +++ b/lib/rbcodec/codecs/libopus/silk/arm/resampler_armv4_asm.S @@ -0,0 +1,356 @@ +/* Copyright (c) 2026 Michael Giacomelli + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the conditions stated in + * silk/resampler_private_IIR_FIR.c are met. + */ + +/* GENERATED by silk/arm/gen_resampler_armv4.py -- edit that, not this. + * + * ARMv4 FIR interpolation for silk_resampler_private_IIR_FIR at the three + * SILK rates. Each step visits two or three fixed phase filters in a fixed + * cycle, so every coefficient is a constant, and on ARM7TDMI a constant + * multiply is cheaper as adds of shifted operands (one cycle each) than as + * mla (three or four, plus loading the coefficient). + * + * Sample-major: each input sample is loaded once a cycle and added into the + * two or three accumulators it feeds, sharing a partial product x*(2^a+-1) + * between them where that saves an add. The rounding bias rides in on + * each accumulator's first add. Every sum is exact in 32 bits: the largest + * phase has sum |c| = 50,045, so |sum| < 2^31 for 16-bit input. + * + * opus_int16 *silk_IIR_FIR__armv4(opus_int16 *out, + * const opus_int16 *buf, int cycles) + * + * Runs `cycles` whole cycles (cycles > 0) and returns the advanced out. + * r0 out, r1 buf, r2 cycles, r3-r5 accumulators, r6/r7 samples, r8/r9 + * partial products, r10 0x7fff, r11 the rounding bias. + */ + +#if defined(__thumb__) || defined(__thumb2__) +#error "resampler_armv4_asm.S must be assembled in ARM mode" +#endif + + .text + .align 2 + +/* 16k: phases 0/8/4, 9 samples a cycle, advancing 2. 82 shifted adds a + cycle, 27.3 an output. */ + .global silk_IIR_FIR_16k_armv4 + .type silk_IIR_FIR_16k_armv4, %function +silk_IIR_FIR_16k_armv4: + push {r4-r11, lr} + mov r10, #0x7f00 + orr r10, r10, #0xff @ 0x7fff, for the clamp + mov r11, #0x4000 @ the rounding bias + ldrsh r6, [r1, #2] @ x1 +.L16k_loop: + add r8, r6, r6, lsl #1 + ldrsh r7, [r1, #4] @ x2 + add r3, r11, r6, lsl #3 @ x1 * -600 + sub r3, r3, r6, lsl #9 + sub r3, r3, r8, lsl #5 + add r4, r11, r6, lsl #7 @ x1 * 896 + add r4, r4, r8, lsl #8 + sub r5, r11, r8, lsl #4 @ x1 * -48 + rsb r8, r7, r7, lsl #5 + ldrsh r6, [r1, #6] @ x3 + add r3, r3, r7 @ x2 * 617 + sub r3, r3, r7, lsl #2 + add r3, r3, r8, lsl #2 + add r3, r3, r8, lsl #4 + add r4, r4, r7, lsl #9 @ x2 * -3487 + sub r4, r4, r8 + sub r4, r4, r8, lsl #7 + sub r5, r5, r7, lsl #1 @ x2 * 758 + add r5, r5, r7, lsl #9 + add r5, r5, r8, lsl #3 + rsb r8, r6, r6, lsl #5 + ldrsh r7, [r1, #8] @ x4 + add r3, r3, r6, lsl #15 @ x3 * 30567 + add r3, r3, r8 + sub r3, r3, r8, lsl #3 + sub r3, r3, r8, lsl #6 + sub r4, r4, r6, lsl #4 @ x3 * 11950 + add r4, r4, r8, lsl #1 + add r4, r4, r8, lsl #7 + add r4, r4, r8, lsl #8 + add r5, r5, r6, lsl #2 @ x3 * -3956 + add r5, r5, r6, lsl #3 + sub r5, r5, r8, lsl #7 + add r8, r7, r7, lsl #1 + ldrsh r6, [r1, #10] @ x5 + rsb r9, r7, r7, lsl #6 + sub r3, r3, r7, lsl #6 @ x4 * 2996 + sub r3, r3, r8, lsl #2 + add r3, r3, r8, lsl #10 + add r4, r4, r7 @ x4 * 26341 + add r4, r4, r8, lsl #13 + sub r4, r4, r9, lsl #2 + add r4, r4, r9, lsl #5 + sub r5, r5, r8 @ x4 * 23973 + sub r5, r5, r8, lsl #5 + add r5, r5, r8, lsl #13 + sub r5, r5, r9, lsl #3 + add r8, r6, r6, lsl #1 + ldrsh r7, [r1, #12] @ x6 + add r9, r6, r6, lsl #8 + sub r3, r3, r8, lsl #5 @ x5 * -1375 + sub r3, r3, r8, lsl #9 + add r3, r3, r9 + sub r4, r4, r8, lsl #1 @ x5 * -3350 + add r4, r4, r8, lsl #8 + sub r4, r4, r9, lsl #4 + sub r5, r5, r6, lsl #10 @ x5 * 15143 + sub r5, r5, r8, lsl #3 + sub r5, r5, r9 + add r5, r5, r9, lsl #6 + add r8, r7, r7, lsl #4 + ldrsh r6, [r1, #14] @ x7 + add r3, r3, r8 @ x6 * 425 + add r3, r3, r8, lsl #3 + add r3, r3, r8, lsl #4 + add r4, r4, r7, lsl #9 @ x6 * 529 + add r4, r4, r8 + add r5, r5, r7 @ x6 * -3957 + add r5, r5, r7, lsl #1 + sub r5, r5, r7, lsl #12 + add r5, r5, r8, lsl #3 + rsb r8, r6, r6, lsl #3 + ldrsh r7, [r1, #16] @ x8 + sub r3, r3, r6, lsl #5 @ x7 * -46 + sub r3, r3, r8, lsl #1 + sub r4, r4, r6, lsl #2 @ x7 * -4 + sub r5, r5, r6 @ x7 * 967 + add r5, r5, r6, lsl #10 + sub r5, r5, r8, lsl #3 + add r5, r5, r7 @ x8 * -107 + ldrsh r6, [r1], #4 @ x0, and advance + add r5, r5, r7, lsl #2 + add r5, r5, r7, lsl #4 + sub r5, r5, r7, lsl #7 + add r8, r6, r6, lsl #1 + sub r3, r3, r8 @ x0 * 189 + add r3, r3, r8, lsl #6 + add r4, r4, r6 @ x0 * -103 + sub r4, r4, r6, lsl #3 + sub r4, r4, r8, lsl #5 + teq r3, r3, lsl #1 @ out of 16 bits? + ldrsh r6, [r1, #2] @ next x1 + mov r8, r3, asr #15 + eormi r8, r10, r3, asr #31 + strh r8, [r0], #2 + teq r4, r4, lsl #1 @ out of 16 bits? + mov r9, r4, asr #15 + eormi r9, r10, r4, asr #31 + strh r9, [r0], #2 + teq r5, r5, lsl #1 @ out of 16 bits? + mov r8, r5, asr #15 + eormi r8, r10, r5, asr #31 + strh r8, [r0], #2 + subs r2, r2, #1 + bne .L16k_loop + pop {r4-r11, pc} + .size silk_IIR_FIR_16k_armv4, .-silk_IIR_FIR_16k_armv4 + +/* 8k: phases 0/4/8, 8 samples a cycle, advancing 1. 79 shifted adds a + cycle, 26.3 an output. */ + .global silk_IIR_FIR_8k_armv4 + .type silk_IIR_FIR_8k_armv4, %function +silk_IIR_FIR_8k_armv4: + push {r4-r11, lr} + mov r10, #0x7f00 + orr r10, r10, #0xff @ 0x7fff, for the clamp + mov r11, #0x4000 @ the rounding bias + ldrsh r6, [r1, #2] @ x1 +.L8k_loop: + add r8, r6, r6, lsl #2 + ldrsh r7, [r1, #4] @ x2 + add r3, r11, r8, lsl #3 @ x1 * -600 + sub r3, r3, r8, lsl #7 + add r4, r11, r6, lsl #7 @ x1 * 758 + sub r4, r4, r8, lsl #1 + add r4, r4, r8, lsl #7 + sub r5, r11, r6, lsl #7 @ x1 * 896 + add r5, r5, r6, lsl #10 + add r8, r7, r7, lsl #1 + ldrsh r6, [r1, #6] @ x3 + rsb r9, r7, r7, lsl #5 + sub r3, r3, r8 @ x2 * 617 + add r3, r3, r9, lsl #2 + add r3, r3, r9, lsl #4 + add r4, r4, r8, lsl #2 @ x2 * -3956 + sub r4, r4, r9, lsl #7 + add r5, r5, r7, lsl #9 @ x2 * -3487 + sub r5, r5, r9 + sub r5, r5, r9, lsl #7 + rsb r8, r6, r6, lsl #4 + ldrsh r7, [r1, #8] @ x4 + add r9, r6, r6, lsl #5 + sub r3, r3, r8, lsl #3 @ x3 * 30567 + add r3, r3, r8, lsl #11 + sub r3, r3, r9 + add r4, r4, r8, lsl #10 @ x3 * 23973 + add r4, r4, r9 + add r4, r4, r9, lsl #2 + add r4, r4, r9, lsl #8 + add r5, r5, r6, lsl #4 @ x3 * 11950 + add r5, r5, r8, lsl #1 + add r5, r5, r8, lsl #9 + add r5, r5, r9, lsl #7 + add r8, r7, r7, lsl #1 + ldrsh r6, [r1, #10] @ x5 + rsb r9, r7, r7, lsl #3 + sub r3, r3, r7, lsl #6 @ x4 * 2996 + sub r3, r3, r8, lsl #2 + add r3, r3, r8, lsl #10 + add r4, r4, r7, lsl #5 @ x4 * 15143 + add r4, r4, r8, lsl #8 + add r4, r4, r9 + add r4, r4, r9, lsl #11 + add r5, r5, r7 @ x4 * 26341 + add r5, r5, r8, lsl #13 + sub r5, r5, r9, lsl #2 + add r5, r5, r9, lsl #8 + rsb r8, r6, r6, lsl #3 + ldrsh r7, [r1, #12] @ x6 + rsb r9, r6, r6, lsl #5 + add r3, r3, r8, lsl #6 @ x5 * -1375 + sub r3, r3, r8, lsl #8 + sub r3, r3, r9 + add r4, r4, r6, lsl #2 @ x5 * -3957 + add r4, r4, r8 + sub r4, r4, r9, lsl #7 + sub r5, r5, r8, lsl #1 @ x5 * -3350 + sub r5, r5, r8, lsl #9 + add r5, r5, r9, lsl #3 + rsb r8, r7, r7, lsl #3 + ldrsh r6, [r1, #14] @ x7 + sub r3, r3, r7, lsl #4 @ x6 * 425 + sub r3, r3, r8 + add r3, r3, r8, lsl #6 + sub r4, r4, r7 @ x6 * 967 + add r4, r4, r7, lsl #10 + sub r4, r4, r8, lsl #3 + add r5, r5, r7 @ x6 * 529 + add r5, r5, r7, lsl #4 + add r5, r5, r7, lsl #9 + add r8, r6, r6, lsl #1 + ldrsh r7, [r1], #2 @ x0, and advance + add r3, r3, r6, lsl #1 @ x7 * -46 + sub r3, r3, r8, lsl #4 + add r4, r4, r6 @ x7 * -107 + sub r4, r4, r8, lsl #2 + sub r4, r4, r8, lsl #5 + sub r5, r5, r6, lsl #2 @ x7 * -4 + add r8, r7, r7, lsl #1 + sub r3, r3, r8 @ x0 * 189 + add r3, r3, r8, lsl #6 + sub r4, r4, r8, lsl #4 @ x0 * -48 + add r5, r5, r7 @ x0 * -103 + sub r5, r5, r7, lsl #3 + sub r5, r5, r8, lsl #5 + teq r3, r3, lsl #1 @ out of 16 bits? + ldrsh r6, [r1, #2] @ next x1 + mov r8, r3, asr #15 + eormi r8, r10, r3, asr #31 + strh r8, [r0], #2 + teq r4, r4, lsl #1 @ out of 16 bits? + mov r9, r4, asr #15 + eormi r9, r10, r4, asr #31 + strh r9, [r0], #2 + teq r5, r5, lsl #1 @ out of 16 bits? + mov r8, r5, asr #15 + eormi r8, r10, r5, asr #31 + strh r8, [r0], #2 + subs r2, r2, #1 + bne .L8k_loop + pop {r4-r11, pc} + .size silk_IIR_FIR_8k_armv4, .-silk_IIR_FIR_8k_armv4 + +/* 12k: phases 0/6, 8 samples a cycle, advancing 1. 55 shifted adds a + cycle, 27.5 an output. */ + .global silk_IIR_FIR_12k_armv4 + .type silk_IIR_FIR_12k_armv4, %function +silk_IIR_FIR_12k_armv4: + push {r4-r11, lr} + mov r10, #0x7f00 + orr r10, r10, #0xff @ 0x7fff, for the clamp + mov r11, #0x4000 @ the rounding bias + ldrsh r6, [r1, #2] @ x1 +.L12k_loop: + add r8, r6, r6, lsl #2 + ldrsh r7, [r1, #4] @ x2 + add r3, r11, r8, lsl #3 @ x1 * -600 + sub r3, r3, r8, lsl #7 + sub r4, r11, r6, lsl #5 @ x1 * 972 + add r4, r4, r6, lsl #10 + sub r4, r4, r8, lsl #2 + rsb r8, r7, r7, lsl #3 + ldrsh r6, [r1, #6] @ x3 + add r3, r3, r7, lsl #9 @ x2 * 617 + sub r3, r3, r8 + add r3, r3, r8, lsl #4 + add r4, r4, r7, lsl #1 @ x2 * -4222 + sub r4, r4, r7, lsl #7 + sub r4, r4, r7, lsl #12 + add r8, r6, r6, lsl #3 + ldrsh r7, [r1, #8] @ x4 + sub r3, r3, r6, lsl #11 @ x3 * 30567 + add r3, r3, r6, lsl #15 + sub r3, r3, r8 + sub r3, r3, r8, lsl #4 + sub r4, r4, r6 @ x3 * 18278 + sub r4, r4, r8 + sub r4, r4, r8, lsl #4 + add r4, r4, r8, lsl #11 + add r8, r7, r7, lsl #1 + ldrsh r6, [r1, #10] @ x5 + sub r3, r3, r7, lsl #6 @ x4 * 2996 + sub r3, r3, r8, lsl #2 + add r3, r3, r8, lsl #10 + sub r4, r4, r7, lsl #8 @ x4 * 21254 + add r4, r4, r8, lsl #1 + sub r4, r4, r8, lsl #10 + add r4, r4, r8, lsl #13 + add r8, r6, r6, lsl #1 + ldrsh r7, [r1, #12] @ x6 + add r9, r6, r6, lsl #5 + add r3, r3, r6, lsl #7 @ x5 * -1375 + sub r3, r3, r8, lsl #9 + add r3, r3, r9 + add r4, r4, r6 @ x5 * -4235 + sub r4, r4, r8, lsl #2 + sub r4, r4, r9, lsl #7 + rsb r8, r7, r7, lsl #3 + ldrsh r6, [r1, #14] @ x7 + sub r3, r3, r7, lsl #4 @ x6 * 425 + sub r3, r3, r8 + add r3, r3, r8, lsl #6 + add r4, r4, r7 @ x6 * 905 + add r4, r4, r7, lsl #3 + add r4, r4, r8, lsl #7 + add r3, r3, r6, lsl #1 @ x7 * -46 + ldrsh r7, [r1], #2 @ x0, and advance + add r3, r3, r6, lsl #4 + sub r3, r3, r6, lsl #6 + sub r4, r4, r6, lsl #4 @ x7 * -80 + sub r4, r4, r6, lsl #6 + add r8, r7, r7, lsl #1 + sub r3, r3, r8 @ x0 * 189 + add r3, r3, r8, lsl #6 + sub r4, r4, r8 @ x0 * -99 + sub r4, r4, r8, lsl #5 + teq r3, r3, lsl #1 @ out of 16 bits? + ldrsh r6, [r1, #2] @ next x1 + mov r8, r3, asr #15 + eormi r8, r10, r3, asr #31 + strh r8, [r0], #2 + teq r4, r4, lsl #1 @ out of 16 bits? + mov r9, r4, asr #15 + eormi r9, r10, r4, asr #31 + strh r9, [r0], #2 + subs r2, r2, #1 + bne .L12k_loop + pop {r4-r11, pc} + .size silk_IIR_FIR_12k_armv4, .-silk_IIR_FIR_12k_armv4 diff --git a/lib/rbcodec/codecs/libopus/silk/arm/resampler_armv5e_asm.S b/lib/rbcodec/codecs/libopus/silk/arm/resampler_armv5e_asm.S new file mode 100644 index 0000000000..99c235295a --- /dev/null +++ b/lib/rbcodec/codecs/libopus/silk/arm/resampler_armv5e_asm.S @@ -0,0 +1,321 @@ +/* Copyright (c) 2026 Michael Giacomelli + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the conditions stated in + * silk/resampler_private_IIR_FIR.c are met. + */ + +/* GENERATED by silk/arm/gen_resampler_armv5e.py -- edit that, not this. + * + * ARMv5E FIR interpolation for silk_resampler_private_IIR_FIR at the three + * SILK rates. Each step visits two or three fixed phase filters in a fixed + * cycle (see the C), so the coefficients are constants, packed two to a word + * in each function's literal pool. The buffer is word aligned, so one load + * brings two samples and smla takes whichever halves a tap needs: + * a window starting on an odd sample costs nothing extra. + * + * Outputs are computed in pairs, as two interleaved multiply-accumulate + * chains, so no smla waits on the one before it. An odd output out splits + * its eight taps across the two chains and sums them. The rounding bias + * starts each accumulator, and qadd doubles with saturation, so the top half + * is exactly SAT16((sum + 2^14) >> 15). + * + * opus_int16 *silk_IIR_FIR__armv5e(opus_int16 *out, + * const opus_int16 *buf, int iters) + * + * buf must be word aligned. Runs `iters` whole iterations (iters > 0) -- + * one cycle at 16 kHz, two at 8 and 12 kHz -- and returns the advanced out. + * Each iteration reads the ten samples from buf and advances it one word. + * r0 out, r1 buf, r2 iters, r3/r4 accumulators, r5-r9 sample words, + * r10/r12 and r11/lr coefficient words for the two chains. + */ + +#if defined(__thumb__) || defined(__thumb2__) +#error "resampler_armv5e_asm.S must be assembled in ARM mode" +#endif + + .text + .align 2 + +/* 16k: 3 outputs an iteration, phases 0/8/4. */ + .global silk_IIR_FIR_16k_armv5e + .type silk_IIR_FIR_16k_armv5e, %function +silk_IIR_FIR_16k_armv5e: + push {r4-r11, lr} +.L16k_loop: + ldr r5, [r1], #4 + ldmia r1, {r6-r9} + ldr r10, .L16k_k0 @ phase 0 at x0 + ldr r11, .L16k_k1 @ phase 8 at x0 + mov r3, #0x4000 @ rounding bias + mov r4, #0x4000 + smlabb r3, r5, r10, r3 + smlabb r4, r5, r11, r4 + ldr r12, .L16k_k2 + smlatt r3, r5, r10, r3 + ldr lr, .L16k_k3 + smlatt r4, r5, r11, r4 + smlabb r3, r6, r12, r3 + smlabb r4, r6, lr, r4 + ldr r10, .L16k_k4 + smlatt r3, r6, r12, r3 + ldr r11, .L16k_k5 + smlatt r4, r6, lr, r4 + smlabb r3, r7, r10, r3 + smlabb r4, r7, r11, r4 + ldr r12, .L16k_k6 + smlatt r3, r7, r10, r3 + ldr lr, .L16k_k7 + smlatt r4, r7, r11, r4 + smlabb r3, r8, r12, r3 + smlabb r4, r8, lr, r4 + smlatt r3, r8, r12, r3 + smlatt r4, r8, lr, r4 + qadd r3, r3, r3 @ SAT16(x >> 15) + qadd r4, r4, r4 @ in the top half + mov r3, r3, asr #16 + mov r4, r4, asr #16 + strh r3, [r0], #2 + strh r4, [r0], #2 + ldr r10, .L16k_k8 @ phase 4 at x1, taps 0-3 + ldr r11, .L16k_k9 @ taps 4-7 + mov r3, #0x4000 @ rounding bias + smlatb r3, r5, r10, r3 + smultb r4, r7, r11 + ldr r12, .L16k_k10 + smlabt r3, r6, r10, r3 + ldr lr, .L16k_k11 + smlabt r4, r8, r11, r4 + smlatb r3, r6, r12, r3 + smlatb r4, r8, lr, r4 + smlabt r3, r7, r12, r3 + smlabt r4, r9, lr, r4 + add r3, r3, r4 + qadd r3, r3, r3 @ SAT16(x >> 15) + mov r3, r3, asr #16 + strh r3, [r0], #2 + subs r2, r2, #1 + bne .L16k_loop + pop {r4-r11, pc} + .align 2 +.L16k_k0: .word 0xfda800bd @ 189, -600 +.L16k_k1: .word 0x0380ff99 @ -103, 896 +.L16k_k2: .word 0x77670269 @ 617, 30567 +.L16k_k3: .word 0x2eaef261 @ -3487, 11950 +.L16k_k4: .word 0xfaa10bb4 @ 2996, -1375 +.L16k_k5: .word 0xf2ea66e5 @ 26341, -3350 +.L16k_k6: .word 0xffd201a9 @ 425, -46 +.L16k_k7: .word 0xfffc0211 @ 529, -4 +.L16k_k8: .word 0x02f6ffd0 @ -48, 758 +.L16k_k9: .word 0xf08b3b27 @ 15143, -3957 +.L16k_k10: .word 0x5da5f08c @ -3956, 23973 +.L16k_k11: .word 0xff9503c7 @ 967, -107 + .size silk_IIR_FIR_16k_armv5e, .-silk_IIR_FIR_16k_armv5e + +/* 8k: 6 outputs an iteration, phases 0/4/8/0/4/8. */ + .global silk_IIR_FIR_8k_armv5e + .type silk_IIR_FIR_8k_armv5e, %function +silk_IIR_FIR_8k_armv5e: + push {r4-r11, lr} +.L8k_loop: + ldr r5, [r1], #4 + ldmia r1, {r6-r9} + ldr r10, .L8k_k0 @ phase 0 at x0 + ldr r11, .L8k_k1 @ phase 4 at x0 + mov r3, #0x4000 @ rounding bias + mov r4, #0x4000 + smlabb r3, r5, r10, r3 + smlabb r4, r5, r11, r4 + ldr r12, .L8k_k2 + smlatt r3, r5, r10, r3 + ldr lr, .L8k_k3 + smlatt r4, r5, r11, r4 + smlabb r3, r6, r12, r3 + smlabb r4, r6, lr, r4 + ldr r10, .L8k_k4 + smlatt r3, r6, r12, r3 + ldr r11, .L8k_k5 + smlatt r4, r6, lr, r4 + smlabb r3, r7, r10, r3 + smlabb r4, r7, r11, r4 + ldr r12, .L8k_k6 + smlatt r3, r7, r10, r3 + ldr lr, .L8k_k7 + smlatt r4, r7, r11, r4 + smlabb r3, r8, r12, r3 + smlabb r4, r8, lr, r4 + smlatt r3, r8, r12, r3 + smlatt r4, r8, lr, r4 + qadd r3, r3, r3 @ SAT16(x >> 15) + qadd r4, r4, r4 @ in the top half + mov r3, r3, asr #16 + mov r4, r4, asr #16 + strh r3, [r0], #2 + strh r4, [r0], #2 + ldr r10, .L8k_k8 @ phase 8 at x0 + ldr r11, .L8k_k0 @ phase 0 at x1 + mov r3, #0x4000 @ rounding bias + mov r4, #0x4000 + smlabb r3, r5, r10, r3 + smlatb r4, r5, r11, r4 + ldr r12, .L8k_k9 + smlatt r3, r5, r10, r3 + ldr lr, .L8k_k2 + smlabt r4, r6, r11, r4 + smlabb r3, r6, r12, r3 + smlatb r4, r6, lr, r4 + ldr r10, .L8k_k10 + smlatt r3, r6, r12, r3 + ldr r11, .L8k_k4 + smlabt r4, r7, lr, r4 + smlabb r3, r7, r10, r3 + smlatb r4, r7, r11, r4 + ldr r12, .L8k_k11 + smlatt r3, r7, r10, r3 + ldr lr, .L8k_k6 + smlabt r4, r8, r11, r4 + smlabb r3, r8, r12, r3 + smlatb r4, r8, lr, r4 + smlatt r3, r8, r12, r3 + smlabt r4, r9, lr, r4 + qadd r3, r3, r3 @ SAT16(x >> 15) + qadd r4, r4, r4 @ in the top half + mov r3, r3, asr #16 + mov r4, r4, asr #16 + strh r3, [r0], #2 + strh r4, [r0], #2 + ldr r10, .L8k_k1 @ phase 4 at x1 + ldr r11, .L8k_k8 @ phase 8 at x1 + mov r3, #0x4000 @ rounding bias + mov r4, #0x4000 + smlatb r3, r5, r10, r3 + smlatb r4, r5, r11, r4 + ldr r12, .L8k_k3 + smlabt r3, r6, r10, r3 + ldr lr, .L8k_k9 + smlabt r4, r6, r11, r4 + smlatb r3, r6, r12, r3 + smlatb r4, r6, lr, r4 + ldr r10, .L8k_k5 + smlabt r3, r7, r12, r3 + ldr r11, .L8k_k10 + smlabt r4, r7, lr, r4 + smlatb r3, r7, r10, r3 + smlatb r4, r7, r11, r4 + ldr r12, .L8k_k7 + smlabt r3, r8, r10, r3 + ldr lr, .L8k_k11 + smlabt r4, r8, r11, r4 + smlatb r3, r8, r12, r3 + smlatb r4, r8, lr, r4 + smlabt r3, r9, r12, r3 + smlabt r4, r9, lr, r4 + qadd r3, r3, r3 @ SAT16(x >> 15) + qadd r4, r4, r4 @ in the top half + mov r3, r3, asr #16 + mov r4, r4, asr #16 + strh r3, [r0], #2 + strh r4, [r0], #2 + subs r2, r2, #1 + bne .L8k_loop + pop {r4-r11, pc} + .align 2 +.L8k_k0: .word 0xfda800bd @ 189, -600 +.L8k_k1: .word 0x02f6ffd0 @ -48, 758 +.L8k_k2: .word 0x77670269 @ 617, 30567 +.L8k_k3: .word 0x5da5f08c @ -3956, 23973 +.L8k_k4: .word 0xfaa10bb4 @ 2996, -1375 +.L8k_k5: .word 0xf08b3b27 @ 15143, -3957 +.L8k_k6: .word 0xffd201a9 @ 425, -46 +.L8k_k7: .word 0xff9503c7 @ 967, -107 +.L8k_k8: .word 0x0380ff99 @ -103, 896 +.L8k_k9: .word 0x2eaef261 @ -3487, 11950 +.L8k_k10: .word 0xf2ea66e5 @ 26341, -3350 +.L8k_k11: .word 0xfffc0211 @ 529, -4 + .size silk_IIR_FIR_8k_armv5e, .-silk_IIR_FIR_8k_armv5e + +/* 12k: 4 outputs an iteration, phases 0/6/0/6. */ + .global silk_IIR_FIR_12k_armv5e + .type silk_IIR_FIR_12k_armv5e, %function +silk_IIR_FIR_12k_armv5e: + push {r4-r11, lr} +.L12k_loop: + ldr r5, [r1], #4 + ldmia r1, {r6-r9} + ldr r10, .L12k_k0 @ phase 0 at x0 + ldr r11, .L12k_k1 @ phase 6 at x0 + mov r3, #0x4000 @ rounding bias + mov r4, #0x4000 + smlabb r3, r5, r10, r3 + smlabb r4, r5, r11, r4 + ldr r12, .L12k_k2 + smlatt r3, r5, r10, r3 + ldr lr, .L12k_k3 + smlatt r4, r5, r11, r4 + smlabb r3, r6, r12, r3 + smlabb r4, r6, lr, r4 + ldr r10, .L12k_k4 + smlatt r3, r6, r12, r3 + ldr r11, .L12k_k5 + smlatt r4, r6, lr, r4 + smlabb r3, r7, r10, r3 + smlabb r4, r7, r11, r4 + ldr r12, .L12k_k6 + smlatt r3, r7, r10, r3 + ldr lr, .L12k_k7 + smlatt r4, r7, r11, r4 + smlabb r3, r8, r12, r3 + smlabb r4, r8, lr, r4 + smlatt r3, r8, r12, r3 + smlatt r4, r8, lr, r4 + qadd r3, r3, r3 @ SAT16(x >> 15) + qadd r4, r4, r4 @ in the top half + mov r3, r3, asr #16 + mov r4, r4, asr #16 + strh r3, [r0], #2 + strh r4, [r0], #2 + ldr r10, .L12k_k0 @ phase 0 at x1 + ldr r11, .L12k_k1 @ phase 6 at x1 + mov r3, #0x4000 @ rounding bias + mov r4, #0x4000 + smlatb r3, r5, r10, r3 + smlatb r4, r5, r11, r4 + ldr r12, .L12k_k2 + smlabt r3, r6, r10, r3 + ldr lr, .L12k_k3 + smlabt r4, r6, r11, r4 + smlatb r3, r6, r12, r3 + smlatb r4, r6, lr, r4 + ldr r10, .L12k_k4 + smlabt r3, r7, r12, r3 + ldr r11, .L12k_k5 + smlabt r4, r7, lr, r4 + smlatb r3, r7, r10, r3 + smlatb r4, r7, r11, r4 + ldr r12, .L12k_k6 + smlabt r3, r8, r10, r3 + ldr lr, .L12k_k7 + smlabt r4, r8, r11, r4 + smlatb r3, r8, r12, r3 + smlatb r4, r8, lr, r4 + smlabt r3, r9, r12, r3 + smlabt r4, r9, lr, r4 + qadd r3, r3, r3 @ SAT16(x >> 15) + qadd r4, r4, r4 @ in the top half + mov r3, r3, asr #16 + mov r4, r4, asr #16 + strh r3, [r0], #2 + strh r4, [r0], #2 + subs r2, r2, #1 + bne .L12k_loop + pop {r4-r11, pc} + .align 2 +.L12k_k0: .word 0xfda800bd @ 189, -600 +.L12k_k1: .word 0x03ccff9d @ -99, 972 +.L12k_k2: .word 0x77670269 @ 617, 30567 +.L12k_k3: .word 0x4766ef82 @ -4222, 18278 +.L12k_k4: .word 0xfaa10bb4 @ 2996, -1375 +.L12k_k5: .word 0xef755306 @ 21254, -4235 +.L12k_k6: .word 0xffd201a9 @ 425, -46 +.L12k_k7: .word 0xffb00389 @ 905, -80 + .size silk_IIR_FIR_12k_armv5e, .-silk_IIR_FIR_12k_armv5e diff --git a/lib/rbcodec/codecs/libopus/silk/macros.h b/lib/rbcodec/codecs/libopus/silk/macros.h index f46e27caaa..870ba089c7 100644 --- a/lib/rbcodec/codecs/libopus/silk/macros.h +++ b/lib/rbcodec/codecs/libopus/silk/macros.h @@ -135,11 +135,11 @@ static OPUS_INLINE opus_int32 silk_CLZ32(opus_int32 in32) (*((Matrix_base_adr) + ((row)+(M)*(column)))) #endif -#ifdef OPUS_ARM_INLINE_ASM +#ifdef OPUS_ARM_ASM_ARMV4_ONLY #include "arm/macros_armv4.h" #endif -#ifdef OPUS_ARM_INLINE_EDSP +#ifdef OPUS_ARM_ASM_ARMV5E_AND_LATER #include "arm/macros_armv5e.h" #endif diff --git a/lib/rbcodec/codecs/libopus/silk/resampler_private_IIR_FIR.c b/lib/rbcodec/codecs/libopus/silk/resampler_private_IIR_FIR.c index 6b2b3a2e18..5f8acdf374 100644 --- a/lib/rbcodec/codecs/libopus/silk/resampler_private_IIR_FIR.c +++ b/lib/rbcodec/codecs/libopus/silk/resampler_private_IIR_FIR.c @@ -33,6 +33,125 @@ POSSIBILITY OF SUCH DAMAGE. #include "resampler_private.h" #include "stack_alloc.h" +#ifndef OPUS_NO_SILK_FIXED_PHASE +/* One output at phase p from eight consecutive samples: the same sum as the + generic loop below, but as plain 16x16 C multiplies the compiler can + schedule, where silk_SMLABB is inline asm on ARMv5E. */ +static OPUS_INLINE opus_int16 silk_IIR_FIR_tap8( + opus_int16 x0, opus_int16 x1, opus_int16 x2, opus_int16 x3, + opus_int16 x4, opus_int16 x5, opus_int16 x6, opus_int16 x7, + opus_int p +) +{ + const opus_int16 *a = silk_resampler_frac_FIR_12[ p ]; + const opus_int16 *b = silk_resampler_frac_FIR_12[ 11 - p ]; + opus_int32 res_Q15; + + res_Q15 = (opus_int32)x0 * a[ 0 ]; + res_Q15 += (opus_int32)x1 * a[ 1 ]; + res_Q15 += (opus_int32)x2 * a[ 2 ]; + res_Q15 += (opus_int32)x3 * a[ 3 ]; + res_Q15 += (opus_int32)x4 * b[ 3 ]; + res_Q15 += (opus_int32)x5 * b[ 2 ]; + res_Q15 += (opus_int32)x6 * b[ 1 ]; + res_Q15 += (opus_int32)x7 * b[ 0 ]; + return (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( res_Q15, 15 ) ); +} + +/* The steps silk_resampler_init produces for 16 and 8 kHz to 48 kHz visit + only three phases, in a fixed cycle, for any batch up to + RESAMPLER_MAX_BATCH_SIZE_IN: the rounding in the step never drifts across + a phase boundary before the index restarts. Checked exhaustively over + every batch length. The third output's window starts off2 samples later, + and each cycle advances adv samples. */ +static OPUS_INLINE opus_int16 *silk_IIR_FIR_cycle3( + opus_int16 *out, const opus_int16 *buf, opus_int32 max_index_Q16, + opus_int32 inc, opus_int p0, opus_int p1, opus_int p2, opus_int off2, + opus_int adv +) +{ + opus_int32 index_Q16 = 0; + + while( index_Q16 < max_index_Q16 ) { + opus_int16 x0 = buf[ 0 ], x1 = buf[ 1 ], x2 = buf[ 2 ], x3 = buf[ 3 ]; + opus_int16 x4 = buf[ 4 ], x5 = buf[ 5 ], x6 = buf[ 6 ], x7 = buf[ 7 ]; + *out++ = silk_IIR_FIR_tap8( x0, x1, x2, x3, x4, x5, x6, x7, p0 ); + if( ( index_Q16 += inc ) >= max_index_Q16 ) break; + *out++ = silk_IIR_FIR_tap8( x0, x1, x2, x3, x4, x5, x6, x7, p1 ); + if( ( index_Q16 += inc ) >= max_index_Q16 ) break; + if( off2 ) { + *out++ = silk_IIR_FIR_tap8( x1, x2, x3, x4, x5, x6, x7, buf[ 8 ], p2 ); + } else { + *out++ = silk_IIR_FIR_tap8( x0, x1, x2, x3, x4, x5, x6, x7, p2 ); + } + index_Q16 += inc; + buf += adv; + } + return out; +} + +/* 12 kHz to 48 kHz steps by exactly half a sample: phases 0 and 6. */ +static OPUS_INLINE opus_int16 *silk_IIR_FIR_cycle2( + opus_int16 *out, const opus_int16 *buf, opus_int32 max_index_Q16 +) +{ + opus_int32 index_Q16 = 0; + + while( index_Q16 < max_index_Q16 ) { + opus_int16 x0 = buf[ 0 ], x1 = buf[ 1 ], x2 = buf[ 2 ], x3 = buf[ 3 ]; + opus_int16 x4 = buf[ 4 ], x5 = buf[ 5 ], x6 = buf[ 6 ], x7 = buf[ 7 ]; + *out++ = silk_IIR_FIR_tap8( x0, x1, x2, x3, x4, x5, x6, x7, 0 ); + if( ( index_Q16 += 32768 ) >= max_index_Q16 ) break; + *out++ = silk_IIR_FIR_tap8( x0, x1, x2, x3, x4, x5, x6, x7, 6 ); + index_Q16 += 32768; + buf += 1; + } + return out; +} + +#if defined(OPUS_ARM_ASM_ARMV4_ONLY) && !defined(OPUS_ARM_NO_SILK_ASM) +/* The same cycles as shifts and adds of constants, in + silk/arm/resampler_armv4_asm.S; each runs whole cycles only. */ +#define SILK_IIR_FIR_ASM +#define SILK_IIR_FIR_16K silk_IIR_FIR_16k_armv4, 43691, 3, 1, 2 +#define SILK_IIR_FIR_8K silk_IIR_FIR_8k_armv4, 21846, 3, 1, 1 +#define SILK_IIR_FIR_12K silk_IIR_FIR_12k_armv4, 32768, 2, 1, 1 +#elif defined(OPUS_ARM_ASM_ARMV5E_AND_LATER) && !defined(OPUS_ARM_NO_SILK_ASM) +/* Packed 16x16 multiply-accumulates, in silk/arm/resampler_armv5e_asm.S. + Each loads two samples a word, so the buffer must be word aligned, and + at 8 and 12 kHz an iteration is two cycles, one word further on. */ +#define SILK_IIR_FIR_ASM +#define SILK_IIR_FIR_ALIGNED +#define SILK_IIR_FIR_16K silk_IIR_FIR_16k_armv5e, 43691, 3, 1, 2 +#define SILK_IIR_FIR_8K silk_IIR_FIR_8k_armv5e, 21846, 3, 2, 2 +#define SILK_IIR_FIR_12K silk_IIR_FIR_12k_armv5e, 32768, 2, 2, 2 +#endif + +#ifdef SILK_IIR_FIR_ASM +opus_int16 *silk_IIR_FIR_16k_armv4( opus_int16 *out, const opus_int16 *buf, opus_int32 iters ); +opus_int16 *silk_IIR_FIR_8k_armv4( opus_int16 *out, const opus_int16 *buf, opus_int32 iters ); +opus_int16 *silk_IIR_FIR_12k_armv4( opus_int16 *out, const opus_int16 *buf, opus_int32 iters ); +opus_int16 *silk_IIR_FIR_16k_armv5e( opus_int16 *out, const opus_int16 *buf, opus_int32 iters ); +opus_int16 *silk_IIR_FIR_8k_armv5e( opus_int16 *out, const opus_int16 *buf, opus_int32 iters ); +opus_int16 *silk_IIR_FIR_12k_armv5e( opus_int16 *out, const opus_int16 *buf, opus_int32 iters ); + +/* Run the kernel over every whole iteration of cyc cycles of n outputs at + step inc, each advancing adv samples, and leave the C the rest. Cycle j + is whole when its last output's index, (j*n + n-1)*inc, is below + max_index_Q16. */ +#define SILK_IIR_FIR_RUN( args ) SILK_IIR_FIR_RUN_( args ) +#define SILK_IIR_FIR_RUN_( fn, inc, n, cyc, adv ) \ + do { \ + opus_int32 iters = ( max_index_Q16 + (inc) - 1 ) / ( (n) * (inc) ) / (cyc); \ + if( iters > 0 ) { \ + out = fn( out, buf, iters ); \ + buf += (adv) * iters; \ + max_index_Q16 -= iters * (cyc) * (n) * (inc); \ + } \ + } while( 0 ) +#endif +#endif + static OPUS_INLINE opus_int16 *silk_resampler_private_IIR_FIR_INTERPOL( opus_int16 *out, opus_int16 *buf, @@ -44,6 +163,26 @@ static OPUS_INLINE opus_int16 *silk_resampler_private_IIR_FIR_INTERPOL( opus_int16 *buf_ptr; opus_int32 table_index; +#ifndef OPUS_NO_SILK_FIXED_PHASE + switch( index_increment_Q16 ) { + case 43691: /* 16 kHz */ +#ifdef SILK_IIR_FIR_ASM + SILK_IIR_FIR_RUN( SILK_IIR_FIR_16K ); +#endif + return silk_IIR_FIR_cycle3( out, buf, max_index_Q16, 43691, 0, 8, 4, 1, 2 ); + case 21846: /* 8 kHz */ +#ifdef SILK_IIR_FIR_ASM + SILK_IIR_FIR_RUN( SILK_IIR_FIR_8K ); +#endif + return silk_IIR_FIR_cycle3( out, buf, max_index_Q16, 21846, 0, 4, 8, 0, 1 ); + case 32768: /* 12 kHz */ +#ifdef SILK_IIR_FIR_ASM + SILK_IIR_FIR_RUN( SILK_IIR_FIR_12K ); +#endif + return silk_IIR_FIR_cycle2( out, buf, max_index_Q16 ); + } +#endif + /* Interpolate upsampled signal and store in output array */ for( index_Q16 = 0; index_Q16 < max_index_Q16; index_Q16 += index_increment_Q16 ) { table_index = silk_SMULWB( index_Q16 & 0xFFFF, 12 ); @@ -72,10 +211,21 @@ void silk_resampler_private_IIR_FIR( silk_resampler_state_struct *S = (silk_resampler_state_struct *)SS; opus_int32 nSamplesIn; opus_int32 max_index_Q16, index_increment_Q16; +#ifdef SILK_IIR_FIR_ALIGNED + VARDECL( opus_int16, buf_alloc ); + opus_int16 *buf; +#else VARDECL( opus_int16, buf ); +#endif SAVE_STACK; +#ifdef SILK_IIR_FIR_ALIGNED + /* One sample spare, to start on a word */ + ALLOC( buf_alloc, 2 * S->batchSize + RESAMPLER_ORDER_FIR_12 + 1, opus_int16 ); + buf = buf_alloc + ( ( (size_t)buf_alloc >> 1 ) & 1 ); +#else ALLOC( buf, 2 * S->batchSize + RESAMPLER_ORDER_FIR_12, opus_int16 ); +#endif /* Copy buffered samples to start of buffer */ silk_memcpy( buf, S->sFIR.i16, RESAMPLER_ORDER_FIR_12 * sizeof( opus_int16 ) ); diff --git a/lib/rbcodec/codecs/librm/rm.c b/lib/rbcodec/codecs/librm/rm.c index 49ec6da0a3..e7d9bcab71 100644 --- a/lib/rbcodec/codecs/librm/rm.c +++ b/lib/rbcodec/codecs/librm/rm.c @@ -32,7 +32,7 @@ #include "inttypes.h" #include -int filesize(int fd) +int ffilesize(int fd) { struct stat buf; diff --git a/lib/rbcodec/codecs/librm/rm.h b/lib/rbcodec/codecs/librm/rm.h index 8e4d124d68..1e110a956f 100644 --- a/lib/rbcodec/codecs/librm/rm.h +++ b/lib/rbcodec/codecs/librm/rm.h @@ -95,7 +95,7 @@ int rm_get_packet(uint8_t **src,RMContext *rmctx, RMPacket *pkt); #ifdef TEST -int filesize(int fd); +int ffilesize(int fd); void advance_buffer(uint8_t **buf, int val); /* Get a (sub_packet_h*frames_per_packet) number of audio frames from a file descriptor */ diff --git a/lib/rbcodec/codecs/libspeex/ltp.c b/lib/rbcodec/codecs/libspeex/ltp.c index 456a184c02..77fb17b050 100644 --- a/lib/rbcodec/codecs/libspeex/ltp.c +++ b/lib/rbcodec/codecs/libspeex/ltp.c @@ -813,7 +813,12 @@ spx_word32_t *cumul_gain } for (i=0;irate<1)goto err_out; if(vi->channels<1)goto err_out; + /* This build decodes at most CHANNELS channels (synthesis.c produces no + PCM for more, and several arrays are sized by it). Refuse the stream + here, before its setup is unpacked: a 5.1 stream's codebooks alone + can take several hundred KB of the codec heap for no output. */ + if(vi->channels>CHANNELS)goto err_out; if(ci->blocksizes[0]<64)goto err_out; if(ci->blocksizes[1]blocksizes[0])goto err_out; if(ci->blocksizes[1]>8192)goto err_out; diff --git a/lib/rbcodec/codecs/libtremor/vorbisfile.c b/lib/rbcodec/codecs/libtremor/vorbisfile.c index b9d8758a86..52a667c44c 100644 --- a/lib/rbcodec/codecs/libtremor/vorbisfile.c +++ b/lib/rbcodec/codecs/libtremor/vorbisfile.c @@ -130,25 +130,45 @@ static ogg_int64_t _get_next_page(OggVorbis_File *vf,ogg_page *og, } } -/* This is a nasty hack to work around the huge allocations we get from - huge comment packets, usually due to embedded album art */ +/* Skip the comment packet without storing it. It can be huge, usually due + to embedded album art, and none of it is used here. + + If only the start of the packet is buffered, that start is forgotten. + ogg_stream_pagein() then drops the rest of the packet by itself, as it + does for any packet whose beginning was lost, so the stream's body and + lacing buffers never grow with the size of the comment. */ static int ogg_stream_discard_packet(OggVorbis_File *vf,ogg_page *og, ogg_int64_t boundary){ - int ret; - while((ret = ogg_stream_packetout(&vf->os, NULL)) == 0) { - if(_get_next_page(vf, og, boundary)<0) - break; - ogg_stream_pagein(&vf->os,og,false); + ogg_stream_state *os=&vf->os; + int ret=ogg_stream_packetout(os,NULL); + int i; + + if(ret<0)return -1; + if(ret>0)return 1; /* the whole packet was buffered, and is now skipped */ + + /* forget the partial packet, as ogg_stream_pagein() does for a page + that is out of sequence */ + for(i=os->lacing_packet;ilacing_fill;i++) + os->body_fill-=os->lacing_vals[i]&0xff; + os->lacing_fill=os->lacing_packet; + + /* read pages until one holds the end of the packet: every lacing value + of a page that only continues it is 255 */ + for(;;){ + int segments; + + if(_get_next_page(vf,og,boundary)<0)return -1; + if(ogg_page_serialno(og)!=os->serialno)continue; + + ogg_stream_pagein(os,og,true); + + segments=og->header[26]; + for(i=0;iheader[27+i]<255)break; + if(ios.body_fill < og->body_len || vf->os.body_storage < vf->os.body_fill) - if(_os_body_expand(&vf->os, vf->os.body_fill - vf->os.body_storage + og->body_len)) - return -1; - memcpy(vf->os.body_data+vf->os.body_fill-og->body_len, og->body, og->body_len); + + os->packetno++; /* count the packet that was skipped */ return 1; } @@ -1287,11 +1307,16 @@ int ov_pcm_seek_page(OggVorbis_File *vf,ogg_int64_t pos){ /* new search algorithm by HB (Nicholas Vinen) */ { ogg_int64_t end=vf->offsets[link+1]; - ogg_int64_t begin=vf->offsets[link]; + /* Search from the first audio page, not from the start of the link + (as libvorbis does): a seek to the beginning must not run the + headers through the stream again, as that buffers the whole + comment packet, album art included. */ + ogg_int64_t begin=vf->dataoffsets[link]; ogg_int64_t begintime = vf->pcmlengths[link*2]; ogg_int64_t endtime = vf->pcmlengths[link*2+1]+begintime; ogg_int64_t target=pos-total+begintime; ogg_int64_t best=begin; + int found=0; /* a page that ends before the target */ ogg_page og; while(beginoffset; /* raw offset of next page */ begintime=granulepos; @@ -1391,8 +1417,14 @@ int ov_pcm_seek_page(OggVorbis_File *vf,ogg_int64_t pos){ ogg_stream_reset_serialno(&vf->os,vf->current_serialno); ogg_stream_pagein(&vf->os,&og,true); + /* The target is in the first audio page, which is where we are + now: decoding starts with its first packet, at the beginning of + the link. */ + if(!found) + vf->pcm_offset=total; + /* pull out all but last packet; the one with granulepos */ - while(1){ + while(found){ result=ogg_stream_packetpeek(&vf->os,&op); if(result==0){ /* !!! the packet finishing this page originated on a diff --git a/lib/rbcodec/codecs/libwma/wmadeci.c b/lib/rbcodec/codecs/libwma/wmadeci.c index 56779ca517..fd852aa3b7 100644 --- a/lib/rbcodec/codecs/libwma/wmadeci.c +++ b/lib/rbcodec/codecs/libwma/wmadeci.c @@ -587,7 +587,8 @@ int wma_decode_init(WMADecodeContext* s, asf_waveformatex_t *wfx) interpolation to reduce the mantissa table size at a small speed expense (linear interpolation approximately doubles the number of bits of precision). */ -static inline fixed32 pow_m1_4(WMADecodeContext *s, fixed32 x) +/* x^(-1/4) in 16.16, for x given with frac_bits fractional bits */ +static inline fixed32 pow_m1_4(WMADecodeContext *s, fixed64 x, int frac_bits) { union { float f; @@ -595,8 +596,18 @@ static inline fixed32 pow_m1_4(WMADecodeContext *s, fixed32 x) } u, t; unsigned int e, m; fixed32 a, b; + int shift = 0; - u.f = fixtof64(x); + /* bring x into 32 bits to convert it, and put that shift and the + fractional bits into the float's exponent */ + while (x >> 31) + { + x >>= 1; + shift++; + } + u.f = (float)(fixed32)x; + if (x != 0) + u.v += (shift - frac_bits) << 23; e = u.v >> 23; m = (u.v >> (23 - LSP_POW_BITS)) & ((1 << LSP_POW_BITS) - 1); /* build interpolation scale: 1 <= t < 2. */ @@ -654,6 +665,7 @@ static void wma_lsp_to_curve(WMADecodeContext *s, { int i, j; fixed32 p, q, w, v, val_max, temp2; + fixed64 v64; val_max = 0; for(i=0;i>9; /* p/q end up as 16.16 */ - v = pow_m1_4(s, v); + /* 2 in 5.27 format is 0x10000000. + * These squares are far above what 16.16 holds where the curve + * is low, and far below one of its steps where the curve peaks, + * so the sum is taken in 64 bits, with 38 fractional bits. */ + v64 = (fixed64)p * fixmul32b(p, (0x10000000 - w)) + + (fixed64)q * fixmul32b(q, (0x10000000 + w)); + v = pow_m1_4(s, v64, 38); if (v > val_max) val_max = v; out[i] = v; @@ -777,6 +790,69 @@ static int decode_exp_vlc(WMADecodeContext *s, int ch) return 0; } +/* The gain of a noise coded band, as ffmpeg computes it in floating point: + * + * mdct_norm * sqrt(power / last_power) * 10^(value/20) + * / (max_exponent * noise_mult) + * + * returned as the 16.16 factor wma_decode_block() applies to the band. + * 1/noise_mult is 50 or 25. The terms differ too much in size for 32 bits, + * and this runs once a band, so it is all done in 64 bits. */ +static fixed32 noise_band_gain(uint64_t power, uint64_t last_power, int value, + fixed32 max_exponent, fixed32 mdct_norm, + int inv_noise_mult) +{ + uint64_t ratio, root, bit, a, b; + int shift = 16 + 20; + + if (power == 0 || max_exponent <= 0) + return 0; + + /* the power ratio in 16.16, kept small enough to take its root */ + ratio = last_power ? (power << 16) / last_power : (uint64_t)1 << 46; + if (ratio > (uint64_t)1 << 46) + ratio = (uint64_t)1 << 46; + + /* its root in 16.16 */ + ratio <<= 16; + root = 0; + for (bit = (uint64_t)1 << 62; bit != 0; bit >>= 2) { + if (ratio >= root + bit) { + ratio -= root + bit; + root = (root >> 1) + bit; + } else { + root >>= 1; + } + } + + a = root * mdct_norm * inv_noise_mult; + b = pow_table[value + 20]; + while (b >> 43) { /* keep the shift below in 64 bits */ + b >>= 1; + shift--; + } + b = (b << 20) / max_exponent; + + /* a * b >> shift, with both brought down to 31 bits first */ + while (a >> 31) { + a >>= 1; + shift--; + } + while (b >> 31) { + b >>= 1; + shift--; + } + a *= b; + if (shift > 0) + a >>= shift; + else if (a >> (31 + shift)) + return 0x7fffffff; + else + a <<= -shift; + + return a > 0x7fffffff ? 0x7fffffff : (fixed32)a; +} + /* return 0 if OK. return 1 if last block of frame. return -1 if unrecorrable error. */ static int wma_decode_block(WMADecodeContext *s) @@ -1064,7 +1140,7 @@ static int wma_decode_block(WMADecodeContext *s) fixed64 mult1; fixed32 noise, temp1, temp2, mult2; int i, j, n, n1, last_high_band, esize; - fixed32 exp_power[HIGH_BAND_MAX_SIZE]; + uint64_t exp_power[HIGH_BAND_MAX_SIZE]; //total_gain, coefs1, mdctnorm are lossless @@ -1098,14 +1174,15 @@ static int wma_decode_block(WMADecodeContext *s) for(i = 0;i < s->coefs_start; ++i) { *coefs++ = fixmul32( (fixmul32(s->noise_table[s->noise_index], - exponents[i<>esize])>>4),Fixed32From64(mult1)) >>2; + exponents[i<>esize])>>4),(fixed32)(mult1>>16)) >>2; s->noise_index = (s->noise_index + 1) & (NOISE_TAB_SIZE - 1); } n1 = s->exponent_high_sizes[bsize]; /* compute power of high bands */ - exponents = s->exponents[ch] +(s->high_band_start[bsize]<exponents[ch] + + (s->high_band_start[bsize]<>esize); last_high_band = 0; /* avoid warning */ for (j=0;jblock_len_bits][j]; if (s->high_band_coded[ch][j]) { - fixed32 e2, v; - e2 = 0; + uint64_t e2 = 0; for(i = 0;i < n; ++i) { - /*v is normalized later on so its fixed format is irrelevant*/ - v = exponents[i<>esize]>>4; - e2 += fixmul32(v, v)>>3; + /* Only ratios of the powers are used, so their + format does not matter; this one cannot + overflow. */ + uint32_t v = exponents[i<>esize]>>6; + e2 += (uint64_t)v * v; } - exp_power[j] = e2/n; /*n is an int...*/ + exp_power[j] = e2/n; last_high_band = j; } - exponents += n<>esize; } /* main freqs and high freqs */ - exponents = s->exponents[ch] + (s->coefs_start<exponents[ch] + (s->coefs_start<>esize); for(j=-1;j= 0 && s->high_band_coded[ch][j]) { /* use noise with specified power */ - fixed32 tmp = fixdiv32(exp_power[j],exp_power[last_high_band]); + fixed32 gain = noise_band_gain(exp_power[j], + exp_power[last_high_band], + s->high_band_values[ch][j], + s->max_exponent[ch], mdct_norm, + s->use_exp_vlc ? 50 : 25); - /*mult1 is 48.16, pow_table is 48.16*/ - mult1 = fixmul32(fixsqrt32(tmp), - pow_table[s->high_band_values[ch][j]+20]) >> 16; - - /*this step has a fairly high degree of error for some reason*/ - mult1 = fixdiv64(mult1,fixmul32(s->max_exponent[ch],s->noise_mult)); - mult1 = mult1*mdct_norm>>PRECISION; for(i = 0;i < n; ++i) { noise = s->noise_table[s->noise_index]; s->noise_index = (s->noise_index + 1) & (NOISE_TAB_SIZE - 1); *coefs++ = fixmul32((fixmul32(exponents[i<>esize],noise)>>4), - Fixed32From64(mult1)) >>2; + gain) >>2; } - exponents += n<>esize; } else { @@ -1176,7 +1251,7 @@ static int wma_decode_block(WMADecodeContext *s) temp2 = fixmul32(exponents[i<>esize], mult>>18); *coefs++ = fixmul32(temp1, temp2); } - exponents += n<>esize; } } diff --git a/lib/rbcodec/codecs/libwmapro/wmaprodec.c b/lib/rbcodec/codecs/libwmapro/wmaprodec.c index 578b8ddb97..75bdf4d8a1 100644 --- a/lib/rbcodec/codecs/libwmapro/wmaprodec.c +++ b/lib/rbcodec/codecs/libwmapro/wmaprodec.c @@ -224,6 +224,8 @@ typedef struct WMAProDecodeCtx { uint8_t len_prefix; ///< frame is prefixed with its length uint8_t dynamic_range_compression; ///< frame contains DRC data uint8_t bits_per_sample; ///< integer audio sample size for the unscaled IMDCT output (used to scale to [-1.0, 1.0]) + uint8_t quant_step_bias; ///< added to the quantization step of a stream of less than 24 bits + int32_t quant_scale; ///< s1.30 factor for its quantization factors, 0 if none uint16_t samples_per_frame; ///< number of samples to output uint16_t log2_frame_size; int8_t num_channels; ///< number of channels in the stream (same as AVCodecContext.num_channels) @@ -332,6 +334,18 @@ int decode_init(asf_waveformatex_t *wfx) s->decode_flags = AV_RL16(edata_ptr+14); channel_mask = AV_RL32(edata_ptr+2); s->bits_per_sample = AV_RL16(edata_ptr); + + /* A stream of fewer bits has a lower quantization step. Decode it at + * the level of a 24 bit stream instead: use that stream's quantization + * step, and scale the factors by what is left, + * 2^(24-bits) / 10^(bias/20). */ + if (s->bits_per_sample == 16) { + s->quant_step_bias = (90 * 24 >> 4) - (90 * 16 >> 4); + s->quant_scale = 1545752065; /* 256 / 10^(45/20) */ + } else if (s->bits_per_sample == 20) { + s->quant_step_bias = (90 * 24 >> 4) - (90 * 20 >> 4); + s->quant_scale = 1216241597; /* 16 / 10^(23/20) */ + } /** dump the extradata */ for (i = 0; i < wfx->datalen; i++) DEBUGF("[%x] ", wfx->data[i]); @@ -1030,6 +1044,38 @@ static int decode_scale_factors(WMAProDecodeCtx* s) return 0; } +/** + *@brief Apply a 2x2 decorrelation matrix to one band of two channels. + * Two channels are by far the common case; the results are the + * same as the general loop's in inverse_channel_transform(). + *@param ch0 first channel's coefficients + *@param ch1 second channel's coefficients + *@param mat the matrix, as 16.16 fixed point + *@param len number of coefficients + */ +static inline void decorrelate_stereo(int32_t *ch0, int32_t *ch1, + const int32_t *mat, int len) +{ + const int32_t m0 = mat[0], m1 = mat[1], m2 = mat[2], m3 = mat[3]; + + if (m0 == ONE_FRACT16 && m1 == -ONE_FRACT16 && + m2 == ONE_FRACT16 && m3 == ONE_FRACT16) { + /* The matrix of a stereo stream. Multiplying by +-1.0 is exact, + * so add and subtract; unsigned, as the multiply's result wraps. */ + for (; len > 0; len--) { + uint32_t a = *ch0, b = *ch1; + *ch0++ = a - b; + *ch1++ = a + b; + } + } else { + for (; len > 0; len--) { + int32_t a = *ch0, b = *ch1; + *ch0++ = fixmul16(m0, a) + fixmul16(m1, b); + *ch1++ = fixmul16(m2, a) + fixmul16(m3, b); + } + } +} + /** *@brief Reconstruct the individual channel data. *@param s codec context @@ -1052,24 +1098,33 @@ static void inverse_channel_transform(WMAProDecodeCtx *s) sfb < s->cur_sfb_offsets + s->num_bands; sfb++) { int y; if (*tb++ == 1) { - /** multiply values with the decorrelation_matrix */ - for (y = sfb[0]; y < FFMIN(sfb[1], s->subframe_len); y++) { - const int32_t* mat = s->chgroup[i].fixdecorrelation_matrix; - const int32_t* data_end = data + num_channels; - int32_t* data_ptr = data; - int32_t** ch; + if (num_channels == 2) { + decorrelate_stereo(ch_data[0] + sfb[0], + ch_data[1] + sfb[0], + s->chgroup[i].fixdecorrelation_matrix, + FFMIN(sfb[1], s->subframe_len) - sfb[0]); + } else { + /** multiply values with the decorrelation_matrix */ + for (y = sfb[0]; + y < FFMIN(sfb[1], s->subframe_len); y++) { + const int32_t* mat = + s->chgroup[i].fixdecorrelation_matrix; + const int32_t* data_end = data + num_channels; + int32_t* data_ptr = data; + int32_t** ch; - for (ch = ch_data; ch < ch_end; ch++) - *data_ptr++ = (*ch)[y]; + for (ch = ch_data; ch < ch_end; ch++) + *data_ptr++ = (*ch)[y]; - for (ch = ch_data; ch < ch_end; ch++) { - int32_t sum = 0; - data_ptr = data; + for (ch = ch_data; ch < ch_end; ch++) { + int32_t sum = 0; + data_ptr = data; - while (data_ptr < data_end) - sum += fixmul16(*mat++, *data_ptr++); + while (data_ptr < data_end) + sum += fixmul16(*mat++, *data_ptr++); - (*ch)[y] = sum; + (*ch)[y] = sum; + } } } } else if (s->num_channels == 2) { @@ -1229,7 +1284,7 @@ static int decode_subframe(WMAProDecodeCtx *s) if (transmit_coeffs) { int step; - int quant_step = 90 * s->bits_per_sample >> 4; + int quant_step = (90 * s->bits_per_sample >> 4) + s->quant_step_bias; /** decode number of vector coded coefficients */ if ((s->transmit_num_vec_coeffs = get_bits1(&s->gb))) { @@ -1330,9 +1385,12 @@ static int decode_subframe(WMAProDecodeCtx *s) DEBUGF("in wmaprodec.c : unhandled value for exp (%d), please report sample.\n", exp); return -1; } - const int32_t quant = QUANT(exp); + int32_t quant = QUANT(exp); int start = s->cur_sfb_offsets[b]; + if (s->quant_scale) + quant = (int64_t)quant * s->quant_scale >> 30; + vector_fixmul_scalar(s->tmp+start, s->channel[c].coeffs + start, quant, end-start); diff --git a/lib/rbcodec/codecs/wma.c b/lib/rbcodec/codecs/wma.c index 92022f8b90..a8b09ced6b 100644 --- a/lib/rbcodec/codecs/wma.c +++ b/lib/rbcodec/codecs/wma.c @@ -178,29 +178,42 @@ new_packet: goto new_packet; } } else if (res > 0) { - wma_decode_superframe_init(&wmadec, audiobuf, audiobufsize); + /* A payload usually holds one codec packet (a superframe) of + * blockalign bytes, but it can hold several. */ + int packetsize = audiobufsize; + int offset; - for (i=0; i < wmadec.nb_frames; i++) + if (wfx.blockalign > 0 && audiobufsize % wfx.blockalign == 0) + packetsize = wfx.blockalign; + + for (offset = 0; offset < audiobufsize; offset += packetsize) { - wmares = wma_decode_superframe_frame(&wmadec, - audiobuf, audiobufsize); + const uint8_t *packet = audiobuf + offset; - ci->yield (); + wma_decode_superframe_init(&wmadec, packet, packetsize); - if (wmares < 0) { - /* Do the above, but for errors in decode. */ - errcount++; - DEBUGF("WMA decode error %d, errcount %d\n",wmares, errcount); - if (errcount > 5) { - return CODEC_ERROR; - } else { - ci->advance_buffer(packetlength); - goto new_packet; + for (i=0; i < wmadec.nb_frames; i++) + { + wmares = wma_decode_superframe_frame(&wmadec, + packet, packetsize); + + ci->yield (); + + if (wmares < 0) { + /* Do the above, but for errors in decode. */ + errcount++; + DEBUGF("WMA decode error %d, errcount %d\n",wmares, errcount); + if (errcount > 5) { + return CODEC_ERROR; + } else { + ci->advance_buffer(packetlength); + goto new_packet; + } + } else if (wmares > 0) { + ci->pcmbuf_insert((*wmadec.frame_out)[0], (*wmadec.frame_out)[1], wmares); + elapsedtime += (wmares*10)/(wfx.rate/100); + ci->set_elapsed(elapsedtime); } - } else if (wmares > 0) { - ci->pcmbuf_insert((*wmadec.frame_out)[0], (*wmadec.frame_out)[1], wmares); - elapsedtime += (wmares*10)/(wfx.rate/100); - ci->set_elapsed(elapsedtime); } } } diff --git a/lib/rbcodec/dsp/afr.c b/lib/rbcodec/dsp/afr.c index 0ec2b2cf4e..dcdafba591 100644 --- a/lib/rbcodec/dsp/afr.c +++ b/lib/rbcodec/dsp/afr.c @@ -137,7 +137,7 @@ static intptr_t afr_configure(struct dsp_proc_entry *this, break; } - return 1; + return 0; } /* Database entry */ diff --git a/lib/rbcodec/dsp/dsp_arm.S b/lib/rbcodec/dsp/dsp_arm.S index dea9eed000..e2eba46aab 100644 --- a/lib/rbcodec/dsp/dsp_arm.S +++ b/lib/rbcodec/dsp/dsp_arm.S @@ -191,6 +191,7 @@ channel_mode_proc_karaoke: */ .section .text, "ax",%progbits .global crossfeed_process + .type crossfeed_process, %function crossfeed_process: @ input: r0 = this, r1 = buf_p @ unfortunately, we ended up in a bit of a register squeeze here, and need @@ -256,6 +257,7 @@ crossfeed_process: */ .section .text .global crossfeed_meier_process + .type crossfeed_meier_process, %function crossfeed_meier_process: @ input: r0 = this, r1 = buf_p ldr r1, [r1] @ r1 = buf = *buf_p; @@ -296,6 +298,7 @@ crossfeed_meier_process: */ .section .text, "ax",%progbits .global resample_hermite + .type resample_hermite, %function resample_hermite: @input: r0 = data, r1 = src, r2 = dst stmfd sp!, { r0-r2, r4-r11, lr } @ stack parms, modified regs @@ -575,6 +578,7 @@ pga_process: .section .text, "ax",%progbits #endif .global filter_process + .type filter_process, %function filter_process: @input: r0 = f, r1 = buf, r2 = count, r3 = channels stmfd sp!, { r4-r11, lr } @ save all clobbered regs diff --git a/lib/rbcodec/dsp/eq.c b/lib/rbcodec/dsp/eq.c index 81c4c972ec..a0839c9de0 100644 --- a/lib/rbcodec/dsp/eq.c +++ b/lib/rbcodec/dsp/eq.c @@ -71,15 +71,22 @@ static void update_band_filter(int band, unsigned int fout) functions */ typeof (filter_pk_coefs) *coef_gen = filter_pk_coefs; - /* Only first and last bands are not peaking filters */ - if (band == 0) - coef_gen = filter_ls_coefs; - else if (band == EQ_NUM_BANDS-1) - coef_gen = filter_hs_coefs; - const struct eq_band_setting *setting = &settings[band]; struct dsp_filter *filter = &eq_data.filters[band]; + switch (setting->type) + { + case EQ_FILTER_LOW_SHELF: + coef_gen = filter_ls_coefs; + break; + case EQ_FILTER_PEAK: + coef_gen = filter_pk_coefs; + break; + case EQ_FILTER_HIGH_SHELF: + coef_gen = filter_hs_coefs; + break; + } + coef_gen(fp_div(setting->cutoff, fout, 32), setting->q ?: 1, setting->gain, filter); } diff --git a/lib/rbcodec/dsp/eq.h b/lib/rbcodec/dsp/eq.h index 6e02999933..419998ab8c 100644 --- a/lib/rbcodec/dsp/eq.h +++ b/lib/rbcodec/dsp/eq.h @@ -23,8 +23,16 @@ /* => support from 3 to 32 bands */ #define EQ_NUM_BANDS 10 +enum eq_filter_type +{ + EQ_FILTER_LOW_SHELF = 0, + EQ_FILTER_PEAK, + EQ_FILTER_HIGH_SHELF, +}; + struct eq_band_setting { + enum eq_filter_type type; int cutoff; /* Hz */ int q; int gain; /* +/- dB */ diff --git a/lib/rbcodec/metadata/a52.c b/lib/rbcodec/metadata/a52.c index 2176b17a4f..ab484d7f2b 100644 --- a/lib/rbcodec/metadata/a52.c +++ b/lib/rbcodec/metadata/a52.c @@ -19,6 +19,7 @@ ****************************************************************************/ #include +#include #include "metadata.h" #include "logf.h" #include "metadata_parsers.h" @@ -42,37 +43,107 @@ static const unsigned short a52_441framesizes[] = 1254 * 2, 1393 * 2, 1394 * 2 }; +/* How far into the file to look for the first frame */ +#define A52_MAX_SCAN (64 * 1024) +#define A52_HEADER_SIZE 6 + +/* Return the size in bytes of the frame starting with hdr, or 0 if hdr is + not a valid frame header. */ +static int a52_frame_size(const unsigned char *hdr) +{ + int frmsizecod = hdr[4] & 0x3f; + int bitrate; + + if ((hdr[0] != 0x0b) || (hdr[1] != 0x77) || (frmsizecod > 37) + || (hdr[5] >= 0x60)) /* bsid >= 12 is not A52 */ + { + return 0; + } + + bitrate = a52_bitrates[frmsizecod >> 1]; + + switch (hdr[4] & 0xc0) + { + case 0x00: + return bitrate * 2 * 2; + case 0x40: + return a52_441framesizes[frmsizecod]; + case 0x80: + return bitrate * 3 * 2; + default: + return 0; + } +} + +/* Find the first frame in the file, which is not at the start if the file + was cut from a longer stream. A match must be followed by a second valid + header so that sync words inside the audio data are skipped. Returns the + offset of the frame and leaves its header in hdr, or -1 if none found. */ +static off_t a52_find_first_frame(int fd, unsigned char *buf, size_t bufsize, + unsigned char *hdr) +{ + unsigned char next[A52_HEADER_SIZE]; + off_t pos = 0; + ssize_t n; + int i, size; + + while (pos < A52_MAX_SCAN) + { + if ((lseek(fd, pos, SEEK_SET) < 0) + || ((n = read(fd, buf, bufsize)) < A52_HEADER_SIZE)) + { + return -1; + } + + for (i = 0; i <= n - A52_HEADER_SIZE; i++) + { + size = a52_frame_size(&buf[i]); + if (!size) + continue; + + /* A frame at the very start is accepted as before, even if the + file holds only one frame */ + if ((pos + i == 0) + || ((lseek(fd, pos + i + size, SEEK_SET) >= 0) + && (read(fd, next, sizeof(next)) == sizeof(next)) + && a52_frame_size(next))) + { + memcpy(hdr, &buf[i], A52_HEADER_SIZE); + return pos + i; + } + } + + /* Overlap so a header split across reads is still found */ + pos += n - (A52_HEADER_SIZE - 1); + } + + return -1; +} + bool get_a52_metadata(int fd, struct mp3entry *id3) { /* Use the trackname part of the id3 structure as a temporary buffer */ unsigned char* buf = (unsigned char *)id3->path; + unsigned char hdr[A52_HEADER_SIZE]; unsigned long totalsamples; + off_t offset; int i; - if ((lseek(fd, 0, SEEK_SET) < 0) || (read(fd, buf, 5) < 5)) - { - return false; - } - - if ((buf[0] != 0x0b) || (buf[1] != 0x77)) + offset = a52_find_first_frame(fd, buf, sizeof(id3->path), hdr); + if (offset < 0) { logf("not an A52/AC3 file\n"); return false; } - i = buf[4] & 0x3e; - - if (i > 36) - { - logf("A52: Invalid frmsizecod: %d\n",i); - return false; - } + i = hdr[4] & 0x3e; id3->bitrate = a52_bitrates[i >> 1]; id3->vbr = false; - id3->filesize = filesize(fd); + id3->filesize = ffilesize(fd); + id3->first_frame_offset = offset; - switch (buf[4] & 0xc0) + switch (hdr[4] & 0xc0) { case 0x00: id3->frequency = 48000; @@ -88,15 +159,10 @@ bool get_a52_metadata(int fd, struct mp3entry *id3) id3->frequency = 32000; id3->bytesperframe = id3->bitrate * 3 * 2; break; - - default: - logf("A52: Invalid samplerate code: 0x%02x\n", buf[4] & 0xc0); - return false; - break; } /* One A52 frame contains 6 blocks, each containing 256 samples */ - totalsamples = id3->filesize / id3->bytesperframe * 6 * 256; + totalsamples = (id3->filesize - offset) / id3->bytesperframe * 6 * 256; id3->length = totalsamples / id3->frequency * 1000; return true; } diff --git a/lib/rbcodec/metadata/aac.c b/lib/rbcodec/metadata/aac.c index e7312829bd..23a430b5b6 100644 --- a/lib/rbcodec/metadata/aac.c +++ b/lib/rbcodec/metadata/aac.c @@ -72,7 +72,7 @@ bool get_aac_metadata(int fd, struct mp3entry *entry) entry->id3v1len = 0; entry->id3v2len = getid3v2len(fd); entry->first_frame_offset = entry->id3v2len; - entry->filesize = filesize(fd) - entry->first_frame_offset; + entry->filesize = ffilesize(fd) - entry->first_frame_offset; entry->needs_upsampling_correction = false; if (entry->id3v2len) diff --git a/lib/rbcodec/metadata/adx.c b/lib/rbcodec/metadata/adx.c index ce7429a259..6f9c642539 100644 --- a/lib/rbcodec/metadata/adx.c +++ b/lib/rbcodec/metadata/adx.c @@ -69,7 +69,7 @@ bool get_adx_metadata(int fd, struct mp3entry* id3) id3->bitrate = id3->frequency * channels * 18 * 8 / 32 / 1000; id3->length = get_long_be(&buf[12]) / id3->frequency * 1000; id3->vbr = false; - id3->filesize = filesize(fd); + id3->filesize = ffilesize(fd); /* get loop info */ if (!memcmp(buf+0x10,"\x01\xF4\x03",3)) { diff --git a/lib/rbcodec/metadata/aiff.c b/lib/rbcodec/metadata/aiff.c index 9b5f772891..2ac0fe3f02 100644 --- a/lib/rbcodec/metadata/aiff.c +++ b/lib/rbcodec/metadata/aiff.c @@ -142,7 +142,7 @@ bool get_aiff_metadata(int fd, struct mp3entry* id3) } id3->vbr = false; /* AIFF files are CBR */ - id3->filesize = filesize(fd); + id3->filesize = ffilesize(fd); } else { diff --git a/lib/rbcodec/metadata/asap.c b/lib/rbcodec/metadata/asap.c index 7524d2c6f3..a5a207e8d9 100644 --- a/lib/rbcodec/metadata/asap.c +++ b/lib/rbcodec/metadata/asap.c @@ -241,7 +241,7 @@ static bool parse_sap_header(int fd, struct mp3entry* id3, int file_len) bool get_asap_metadata(int fd, struct mp3entry* id3) { - int filelength = filesize(fd); + int filelength = ffilesize(fd); if(parse_sap_header(fd, id3, filelength) == false) { diff --git a/lib/rbcodec/metadata/asf.c b/lib/rbcodec/metadata/asf.c index cf260ad752..6dadda2247 100644 --- a/lib/rbcodec/metadata/asf.c +++ b/lib/rbcodec/metadata/asf.c @@ -647,7 +647,7 @@ bool get_asf_metadata(int fd, struct mp3entry* id3) header. */ id3->first_frame_offset = lseek(fd, 0, SEEK_CUR) + 26; - id3->filesize = filesize(fd); + id3->filesize = ffilesize(fd); /* We copy the wfx struct to the MP3 TOC field in the id3 struct so the codec doesn't need to parse the header object again */ memcpy(id3->toc, &wfx, sizeof(wfx)); diff --git a/lib/rbcodec/metadata/au.c b/lib/rbcodec/metadata/au.c index 7cdd975c7e..52b24823ca 100644 --- a/lib/rbcodec/metadata/au.c +++ b/lib/rbcodec/metadata/au.c @@ -58,7 +58,7 @@ bool get_au_metadata(int fd, struct mp3entry* id3) int offset; id3->vbr = false; /* All Sun audio files are CBR */ - id3->filesize = filesize(fd); + id3->filesize = ffilesize(fd); id3->length = 0; lseek(fd, 0, SEEK_SET); diff --git a/lib/rbcodec/metadata/ay.c b/lib/rbcodec/metadata/ay.c index 4230b44da0..d4c6cc8880 100644 --- a/lib/rbcodec/metadata/ay.c +++ b/lib/rbcodec/metadata/ay.c @@ -135,7 +135,7 @@ bool get_ay_metadata(int fd, struct mp3entry* id3) return false; id3->vbr = false; - id3->filesize = filesize(fd); + id3->filesize = ffilesize(fd); id3->bitrate = 706; id3->frequency = 44100; diff --git a/lib/rbcodec/metadata/flac.c b/lib/rbcodec/metadata/flac.c index 6ff69f65a5..c5d26f08e3 100644 --- a/lib/rbcodec/metadata/flac.c +++ b/lib/rbcodec/metadata/flac.c @@ -121,7 +121,7 @@ bool get_flac_metadata(int fd, struct mp3entry* id3) } id3->vbr = true; /* All FLAC files are VBR */ - id3->filesize = filesize(fd); + id3->filesize = ffilesize(fd); id3->frequency = (buf[10] << 12) | (buf[11] << 4) | ((buf[12] & 0xf0) >> 4); rc = true; /* Got vital metadata */ diff --git a/lib/rbcodec/metadata/gbs.c b/lib/rbcodec/metadata/gbs.c index 9b02952e47..e83395dc34 100644 --- a/lib/rbcodec/metadata/gbs.c +++ b/lib/rbcodec/metadata/gbs.c @@ -52,7 +52,7 @@ bool get_gbs_metadata(int fd, struct mp3entry* id3) return false; id3->vbr = false; - id3->filesize = filesize(fd); + id3->filesize = ffilesize(fd); /* we only render 16 bits, 44.1KHz, Stereo */ id3->bitrate = 706; id3->frequency = 44100; diff --git a/lib/rbcodec/metadata/hes.c b/lib/rbcodec/metadata/hes.c index 87398461d7..f9eab6a0e7 100644 --- a/lib/rbcodec/metadata/hes.c +++ b/lib/rbcodec/metadata/hes.c @@ -26,7 +26,7 @@ bool get_hes_metadata(int fd, struct mp3entry* id3) return false; id3->vbr = false; - id3->filesize = filesize(fd); + id3->filesize = ffilesize(fd); /* we only render 16 bits, 44.1KHz, Stereo */ id3->bitrate = 706; id3->frequency = 44100; diff --git a/lib/rbcodec/metadata/kss.c b/lib/rbcodec/metadata/kss.c index 195e8be38d..bd85a02a4f 100644 --- a/lib/rbcodec/metadata/kss.c +++ b/lib/rbcodec/metadata/kss.c @@ -40,7 +40,7 @@ bool get_kss_metadata(int fd, struct mp3entry* id3) return false; id3->vbr = false; - id3->filesize = filesize(fd); + id3->filesize = ffilesize(fd); /* we only render 16 bits, 44.1KHz, Stereo */ id3->bitrate = 706; id3->frequency = 44100; diff --git a/lib/rbcodec/metadata/metadata.c b/lib/rbcodec/metadata/metadata.c index dd41fdbaa7..e2ca3662c5 100644 --- a/lib/rbcodec/metadata/metadata.c +++ b/lib/rbcodec/metadata/metadata.c @@ -41,7 +41,7 @@ static bool get_shn_metadata(int fd, struct mp3entry *id3) { /* TODO: read the id3v2 header if it exists */ id3->vbr = true; - id3->filesize = filesize(fd); + id3->filesize = ffilesize(fd); return skip_id3v2(fd, id3); } @@ -50,7 +50,7 @@ static bool get_other_asap_metadata(int fd, struct mp3entry *id3) id3->bitrate = 706; id3->frequency = 44100; id3->vbr = false; - id3->filesize = filesize(fd); + id3->filesize = ffilesize(fd); id3->genre_string = id3_get_num_genre(36); /* GAME */ return true; } diff --git a/lib/rbcodec/metadata/mod.c b/lib/rbcodec/metadata/mod.c index 746a4f6022..30f6658a2f 100644 --- a/lib/rbcodec/metadata/mod.c +++ b/lib/rbcodec/metadata/mod.c @@ -91,11 +91,11 @@ bool get_mod_metadata(int fd, struct mp3entry* id3) return false; id3->title = id3->id3v2buf; /* Point title to previous read ID3 buffer. */ - id3->bitrate = filesize(fd)/1024; /* size in kb */ id3->frequency = 44100; id3->length = 120*1000; id3->vbr = false; - id3->filesize = filesize(fd); + id3->filesize = ffilesize(fd); + id3->bitrate = id3->filesize / 1024; /* size in kb */ return true; } diff --git a/lib/rbcodec/metadata/monkeys.c b/lib/rbcodec/metadata/monkeys.c index 382aa65498..7ea4426b46 100644 --- a/lib/rbcodec/metadata/monkeys.c +++ b/lib/rbcodec/metadata/monkeys.c @@ -82,7 +82,7 @@ bool get_monkeys_metadata(int fd, struct mp3entry* id3) } id3->vbr = true; /* All APE files are VBR */ - id3->filesize = filesize(fd); + id3->filesize = ffilesize(fd); totalsamples = finalframeblocks; if (totalframes > 1) diff --git a/lib/rbcodec/metadata/mp3.c b/lib/rbcodec/metadata/mp3.c index b2466f82a0..733dfdbee2 100644 --- a/lib/rbcodec/metadata/mp3.c +++ b/lib/rbcodec/metadata/mp3.c @@ -162,7 +162,7 @@ static int getsonglength(int fd, struct mp3entry *entry) bool get_mp3_metadata(int fd, struct mp3entry *entry) { entry->title = NULL; - entry->filesize = filesize(fd); + entry->filesize = ffilesize(fd); entry->id3v1len = getid3v1len(fd); entry->id3v2len = getid3v2len(fd); diff --git a/lib/rbcodec/metadata/mp3data.c b/lib/rbcodec/metadata/mp3data.c index 5c2db376e5..45424d92e5 100644 --- a/lib/rbcodec/metadata/mp3data.c +++ b/lib/rbcodec/metadata/mp3data.c @@ -608,7 +608,7 @@ int get_mp3file_info(int fd, struct mp3info *info) if(result) return result; - info->byte_count = filesize(fd) - getid3v1len(fd) - offset - bytecount; + info->byte_count = ffilesize(fd) - getid3v1len(fd) - offset - bytecount; } return bytecount; diff --git a/lib/rbcodec/metadata/mp4.c b/lib/rbcodec/metadata/mp4.c index f02d301077..9d6e9e4334 100644 --- a/lib/rbcodec/metadata/mp4.c +++ b/lib/rbcodec/metadata/mp4.c @@ -823,7 +823,7 @@ bool get_mp4_metadata(int fd, struct mp3entry* id3) id3->filesize = 0; errno = 0; - if (read_mp4_container(fd, id3, filesize(fd)) && (errno == 0) + if (read_mp4_container(fd, id3, ffilesize(fd)) && (errno == 0) && (id3->samples > 0) && (id3->frequency > 0) && (id3->filesize > 0)) { diff --git a/lib/rbcodec/metadata/mpc.c b/lib/rbcodec/metadata/mpc.c index 326cc471ec..5db0e0041d 100644 --- a/lib/rbcodec/metadata/mpc.c +++ b/lib/rbcodec/metadata/mpc.c @@ -211,7 +211,7 @@ bool get_musepack_metadata(int fd, struct mp3entry *id3) return false; } - id3->filesize = filesize(fd); + id3->filesize = ffilesize(fd); id3->bitrate = id3->filesize * 8 / id3->length; read_ape_tags(fd, id3); diff --git a/lib/rbcodec/metadata/nsf.c b/lib/rbcodec/metadata/nsf.c index 3184897e22..5a59470163 100644 --- a/lib/rbcodec/metadata/nsf.c +++ b/lib/rbcodec/metadata/nsf.c @@ -262,7 +262,7 @@ bool get_nsf_metadata(int fd, struct mp3entry* id3) return false; id3->vbr = false; - id3->filesize = filesize(fd); + id3->filesize = ffilesize(fd); /* we only render 16 bits, 44.1KHz, Mono */ id3->bitrate = 706; id3->frequency = 44100; diff --git a/lib/rbcodec/metadata/ogg.c b/lib/rbcodec/metadata/ogg.c index b172877351..b6fb3ab482 100644 --- a/lib/rbcodec/metadata/ogg.c +++ b/lib/rbcodec/metadata/ogg.c @@ -133,7 +133,7 @@ bool get_ogg_metadata(int fd, struct mp3entry* id3) return false; } - id3->filesize = filesize(fd); + id3->filesize = ffilesize(fd); /* We need to ensure the serial number from this page is the same as the * one from the last page (since we only support a single bitstream). diff --git a/lib/rbcodec/metadata/oma.c b/lib/rbcodec/metadata/oma.c index 3afa1aad70..42fc275412 100644 --- a/lib/rbcodec/metadata/oma.c +++ b/lib/rbcodec/metadata/oma.c @@ -179,7 +179,7 @@ bool get_oma_metadata(int fd, struct mp3entry* id3) /* Currently, there's no means of knowing the duration * * directly from the the file so we calculate it. */ - id3->filesize = filesize(fd); + id3->filesize = ffilesize(fd); id3->length = ((id3->filesize - id3->first_frame_offset) * 8) / id3->bitrate; return true; } diff --git a/lib/rbcodec/metadata/replaygain.c b/lib/rbcodec/metadata/replaygain.c index 3283a7fd67..864a2c74ea 100644 --- a/lib/rbcodec/metadata/replaygain.c +++ b/lib/rbcodec/metadata/replaygain.c @@ -138,6 +138,26 @@ static long get_replaygain(const char* str) return fp_atof(str, FP_BITS); } +/* Get the gain in Q19.12 format from an Opus R128 gain tag (RFC 7845). + * Returns false if the tag is not a valid integer. + * + * str Gain in dB as a Q7.8 integer, relative to -23 LUFS. + */ +static bool get_r128_gain(const char* str, long* gain) +{ + char* end; + long q8 = strtol(str, &end, 10); + + if (end == str || q8 < -32768 || q8 > 32767) + { + return false; + } + + /* ReplayGain targets -18 LUFS, so add 5 dB to match its loudness */ + *gain = q8 * (FP_ONE / 256) + 5 * FP_ONE; + return true; +} + /* Get the peak volume in Q7.24 format. * * str Peak volume. Full scale is specified as "1.0". Returns 0 for no peak. @@ -158,7 +178,8 @@ long get_replaygain_int(long int_gain) /* Parse a ReplayGain tag conforming to the "VorbisGain standard". If a * valid tag is found, update mp3entry struct accordingly. Existing values - * are not overwritten. + * are not overwritten, except by the R128 gain tags in Opus files, which + * take priority over REPLAYGAIN tags. * * key Name of the tag. * value Value of the tag. @@ -170,9 +191,30 @@ void parse_replaygain(const char* key, const char* value, static const char *rg_options[] = {"replaygain_track_gain", "rg_radio", "replaygain_album_gain", "rg_audiophile", "replaygain_track_peak", "rg_peak", - "replaygain_album_peak", NULL}; + "replaygain_album_peak", + "r128_track_gain", "r128_album_gain", + NULL}; int rg_op = string_option(key, rg_options, true); + long gain; + + if (rg_op == 7 || rg_op == 8) + { /*r128_track_gain||r128_album_gain, only defined for Opus*/ + if (entry->codectype == AFMT_OPUS && get_r128_gain(value, &gain)) + { + if (rg_op == 7) + { + entry->track_level = gain; + entry->track_gain = convert_gain(gain); + } + else + { + entry->album_level = gain; + entry->album_gain = convert_gain(gain); + } + } + return; + } if ((rg_op == 0 || rg_op == 1) && !entry->track_gain) { /*replaygain_track_gain||rg_radio*/ diff --git a/lib/rbcodec/metadata/rm.c b/lib/rbcodec/metadata/rm.c index 0bffe6dfdb..25f49c1880 100644 --- a/lib/rbcodec/metadata/rm.c +++ b/lib/rbcodec/metadata/rm.c @@ -266,7 +266,7 @@ static inline int rm_parse_header(int fd, RMContext *rmctx, struct mp3entry *id3 rmctx->data_offset = skipped + 8; rmctx->bit_rate = rmctx->block_align * rmctx->sample_rate / 192; if (rmctx->block_align) - rmctx->nb_packets = (filesize(fd) - rmctx->data_offset) / rmctx->block_align; + rmctx->nb_packets = (ffilesize(fd) - rmctx->data_offset) / rmctx->block_align; if (rmctx->sample_rate) rmctx->duration = (uint32_t)(256LL * 6 * 1000 * rmctx->nb_packets / rmctx->sample_rate); rmctx->flags |= RM_RAW_DATASTREAM; @@ -504,6 +504,6 @@ bool get_rm_metadata(int fd, struct mp3entry* id3) id3->bitrate = (rmctx->bit_rate + 500) / 1000; id3->frequency = rmctx->sample_rate; id3->length = rmctx->duration; - id3->filesize = filesize(fd); + id3->filesize = ffilesize(fd); return true; } diff --git a/lib/rbcodec/metadata/sgc.c b/lib/rbcodec/metadata/sgc.c index 11a574139f..92a7820d10 100644 --- a/lib/rbcodec/metadata/sgc.c +++ b/lib/rbcodec/metadata/sgc.c @@ -54,7 +54,7 @@ bool get_sgc_metadata(int fd, struct mp3entry* id3) return false; id3->vbr = false; - id3->filesize = filesize(fd); + id3->filesize = ffilesize(fd); /* we only render 16 bits, 44.1KHz, Stereo */ id3->bitrate = 706; id3->frequency = 44100; diff --git a/lib/rbcodec/metadata/sid.c b/lib/rbcodec/metadata/sid.c index 43f693e985..94bafad70d 100644 --- a/lib/rbcodec/metadata/sid.c +++ b/lib/rbcodec/metadata/sid.c @@ -82,7 +82,7 @@ bool get_sid_metadata(int fd, struct mp3entry* id3) */ id3->length = (buf[0xf]-1)*1000; id3->vbr = false; - id3->filesize = filesize(fd); + id3->filesize = ffilesize(fd); return true; } diff --git a/lib/rbcodec/metadata/smaf.c b/lib/rbcodec/metadata/smaf.c index 479c39032b..130edb98fa 100644 --- a/lib/rbcodec/metadata/smaf.c +++ b/lib/rbcodec/metadata/smaf.c @@ -445,7 +445,7 @@ bool get_smaf_metadata(int fd, struct mp3entry* id3) id3->composer = NULL; id3->vbr = false; /* All SMAF files are CBR */ - id3->filesize = filesize(fd); + id3->filesize = ffilesize(fd); /* check File Chunk and Contents Info Chunk */ lseek(fd, 0, SEEK_SET); diff --git a/lib/rbcodec/metadata/spc.c b/lib/rbcodec/metadata/spc.c index 64e00ef830..d2a03fd0e4 100644 --- a/lib/rbcodec/metadata/spc.c +++ b/lib/rbcodec/metadata/spc.c @@ -124,7 +124,7 @@ bool get_spc_metadata(int fd, struct mp3entry* id3) id3->frequency = 32000; id3->length = length+fade; - id3->filesize = filesize(fd); + id3->filesize = ffilesize(fd); id3->genre_string = id3_get_num_genre(36); return true; diff --git a/lib/rbcodec/metadata/tta.c b/lib/rbcodec/metadata/tta.c index ef0f341f29..4d2cfa84a7 100644 --- a/lib/rbcodec/metadata/tta.c +++ b/lib/rbcodec/metadata/tta.c @@ -53,7 +53,7 @@ static void read_id3_tags(int fd, struct mp3entry* id3) { id3->title = NULL; - id3->filesize = filesize(fd); + id3->filesize = ffilesize(fd); id3->id3v2len = getid3v2len(fd); id3->vbr = false; /* All TTA files are CBR */ diff --git a/lib/rbcodec/metadata/vgm.c b/lib/rbcodec/metadata/vgm.c index 03afb15a92..28a2a07b8a 100644 --- a/lib/rbcodec/metadata/vgm.c +++ b/lib/rbcodec/metadata/vgm.c @@ -153,7 +153,7 @@ bool get_vgm_metadata(int fd, struct mp3entry* id3) } id3->vbr = false; - id3->filesize = filesize(fd); + id3->filesize = ffilesize(fd); id3->bitrate = 1411; id3->frequency = 44100; diff --git a/lib/rbcodec/metadata/vox.c b/lib/rbcodec/metadata/vox.c index dcf4e057ef..22a24101c9 100644 --- a/lib/rbcodec/metadata/vox.c +++ b/lib/rbcodec/metadata/vox.c @@ -41,7 +41,7 @@ bool get_vox_metadata(int fd, struct mp3entry* id3) id3->frequency = 8000; id3->bitrate = 8000 * 4 / 1000; id3->vbr = false; /* All VOX files are CBR */ - id3->filesize = filesize(fd); + id3->filesize = ffilesize(fd); id3->length = id3->filesize >> 2; return true; diff --git a/lib/rbcodec/metadata/vtx.c b/lib/rbcodec/metadata/vtx.c index 65401be8b8..e27ae170c9 100644 --- a/lib/rbcodec/metadata/vtx.c +++ b/lib/rbcodec/metadata/vtx.c @@ -102,7 +102,7 @@ bool get_vtx_metadata(int fd, struct mp3entry* id3) if (lseek(fd, 0, SEEK_SET) < 0) goto exit_bad; - if (filesize(fd) < 20) + if (ffilesize(fd) < 20) goto exit_bad; uint hdr = Reader_ReadWord(fd); @@ -142,7 +142,7 @@ bool get_vtx_metadata(int fd, struct mp3entry* id3) id3->bitrate = 706; id3->frequency = 44100; // XXX allow this to be configured? - id3->filesize = filesize(fd); + id3->filesize = ffilesize(fd); id3->length = info.frames * 1000 / info.playerfreq; return true; diff --git a/lib/rbcodec/metadata/wave.c b/lib/rbcodec/metadata/wave.c index 9ecc816832..7d0b9e8e9b 100644 --- a/lib/rbcodec/metadata/wave.c +++ b/lib/rbcodec/metadata/wave.c @@ -305,7 +305,7 @@ static bool read_header(int fd, struct mp3entry* id3, const unsigned char *chunk memset(&fmt, 0, sizeof(struct wave_fmt)); id3->vbr = false; /* All Wave/Wave64 files are CBR */ - id3->filesize = filesize(fd); + id3->filesize = ffilesize(fd); /* get RIFF chunk header */ lseek(fd, 0, SEEK_SET); diff --git a/lib/rbcodec/metadata/wavpack.c b/lib/rbcodec/metadata/wavpack.c index 30be661216..18014f00e5 100644 --- a/lib/rbcodec/metadata/wavpack.c +++ b/lib/rbcodec/metadata/wavpack.c @@ -78,7 +78,7 @@ bool get_wavpack_metadata(int fd, struct mp3entry* id3) } id3->vbr = true; /* All WavPack files are VBR */ - id3->filesize = filesize (fd); + id3->filesize = ffilesize (fd); /* check up to 16 headers before we give up finding one with audio */ diff --git a/lib/rbcodec/rbcodecplatform-unix.h b/lib/rbcodec/rbcodecplatform-unix.h index 9ce01a0035..b25aee24a3 100644 --- a/lib/rbcodec/rbcodecplatform-unix.h +++ b/lib/rbcodec/rbcodecplatform-unix.h @@ -50,7 +50,7 @@ #endif /* filesize */ -off_t filesize(int fd); +off_t ffilesize(int fd); /* snprintf */ #include diff --git a/lib/rbcodec/test/file.h b/lib/rbcodec/test/file.h index ddf9a1d7a4..e37c986df9 100644 --- a/lib/rbcodec/test/file.h +++ b/lib/rbcodec/test/file.h @@ -3,4 +3,4 @@ #include #include -off_t filesize(int fd); +off_t ffilesize(int fd); diff --git a/lib/rbcodec/test/warble.c b/lib/rbcodec/test/warble.c index fa243a3913..4eab14762d 100644 --- a/lib/rbcodec/test/warble.c +++ b/lib/rbcodec/test/warble.c @@ -88,7 +88,7 @@ int find_first_set_bit(uint32_t value) return __builtin_ctz(value); } -off_t filesize(int fd) +off_t ffilesize(int fd) { struct stat st; fstat(fd, &st); @@ -647,6 +647,73 @@ static void ci_logf(const char *fmt, ...) static void stub_void_void(void) { } +#ifdef HAVE_RECORDING +/* warble only ever loads decoders, so the encoder half of the codec API is + never entered. Fail loudly instead of leaving these NULL, which would turn + a mistake into a segfault. */ +static void stub_enc_unsupported(const char *func) +{ + fprintf(stderr, "error: warble does not support encoding (%s)\n", func); + abort(); +} + +static int ci_enc_pcmbuf_read(void *buf, int count) +{ + (void)buf; (void)count; + stub_enc_unsupported(__func__); + return 0; +} + +static int ci_enc_pcmbuf_advance(int count) +{ + (void)count; + stub_enc_unsupported(__func__); + return 0; +} + +static struct enc_chunk_data *ci_enc_encbuf_get_buffer(size_t need) +{ + (void)need; + stub_enc_unsupported(__func__); + return NULL; +} + +static void ci_enc_encbuf_finish_buffer(void) +{ + stub_enc_unsupported(__func__); +} + +static ssize_t ci_enc_stream_read(void *buf, size_t count) +{ + (void)buf; (void)count; + stub_enc_unsupported(__func__); + return -1; +} + +static off_t ci_enc_stream_lseek(off_t offset, int whence) +{ + (void)offset; (void)whence; + stub_enc_unsupported(__func__); + return -1; +} + +static ssize_t ci_enc_stream_write(const void *buf, size_t count) +{ + (void)buf; (void)count; + stub_enc_unsupported(__func__); + return -1; +} + +static int ci_round_value_to_list32(unsigned long value, + const unsigned long list[], + int count, bool signd) +{ + (void)value; (void)list; (void)count; (void)signd; + stub_enc_unsupported(__func__); + return 0; +} +#endif /* HAVE_RECORDING */ + static struct codec_api ci = { 0, /* filesize */ @@ -703,21 +770,14 @@ static struct codec_api ci = { qsort, #ifdef HAVE_RECORDING - ci_enc_get_inputs, - ci_enc_set_parameters, - ci_enc_get_chunk, - ci_enc_finish_chunk, - ci_enc_get_pcm_data, - ci_enc_unget_pcm_data, - - /* file */ - open, - close, - read, - lseek, - write, + ci_enc_pcmbuf_read, + ci_enc_pcmbuf_advance, + ci_enc_encbuf_get_buffer, + ci_enc_encbuf_finish_buffer, + ci_enc_stream_read, + ci_enc_stream_lseek, + ci_enc_stream_write, ci_round_value_to_list32, - #endif /* HAVE_RECORDING */ }; @@ -779,12 +839,15 @@ static void decode_file(const char *input_fn) /* Set up ci */ struct mp3entry id3; + /* get_metadata() doesn't set every field; don't leave stack garbage in the + pointers that print_mp3entry() dereferences */ + memset(&id3, 0, sizeof(id3)); if (!get_metadata(&id3, input_fd, input_fn)) { fprintf(stderr, "error: metadata parsing failed\n"); exit(1); } print_mp3entry(&id3, stderr); - ci.filesize = filesize(input_fd); + ci.filesize = ffilesize(input_fd); ci.id3 = &id3; if (use_dsp) { ci.dsp = dsp_get_config(CODEC_IDX_AUDIO); diff --git a/lib/skin_parser/tag_table.c b/lib/skin_parser/tag_table.c index a13e58a316..2e74c440b3 100644 --- a/lib/skin_parser/tag_table.c +++ b/lib/skin_parser/tag_table.c @@ -252,6 +252,13 @@ static const struct tag_info legal_tags[] = TAG(SKIN_TOKEN_REC_SECONDS, "Rs" , "", SKIN_REFRESH_DYNAMIC), TAG(SKIN_TOKEN_REC_MINUTES, "Rn" , "", SKIN_REFRESH_DYNAMIC), TAG(SKIN_TOKEN_REC_HOURS, "Rh" , "", SKIN_REFRESH_DYNAMIC), + TAG(SKIN_TOKEN_REC_SIZE, "RS" , "", SKIN_REFRESH_DYNAMIC), + TAG(SKIN_TOKEN_REC_PRERECORD, "RP" , "", SKIN_REFRESH_DYNAMIC), + TAG(SKIN_TOKEN_REC_CLIPCOUNT, "Rc" , "", SKIN_REFRESH_DYNAMIC), + TAG(SKIN_TOKEN_REC_TRIGGER, "Rt" , "", SKIN_REFRESH_DYNAMIC), + TAG(SKIN_TOKEN_REC_WARNING, "Rw" , "", SKIN_REFRESH_DYNAMIC), + TAG(SKIN_TOKEN_REC_SOURCE, "Ri" , "", SKIN_REFRESH_DYNAMIC), + TAG(SKIN_TOKEN_REC_GAIN, "Rg" , "", SKIN_REFRESH_DYNAMIC), /* Skin variables */ TAG(SKIN_TOKEN_VAR_SET, "vs", "SSi|I", SKIN_REFRESH_DYNAMIC), diff --git a/lib/skin_parser/tag_table.h b/lib/skin_parser/tag_table.h index eb2d409487..92647c1bdd 100644 --- a/lib/skin_parser/tag_table.h +++ b/lib/skin_parser/tag_table.h @@ -276,6 +276,13 @@ enum skin_token_type { SKIN_TOKEN_REC_SECONDS, SKIN_TOKEN_REC_MINUTES, SKIN_TOKEN_REC_HOURS, + SKIN_TOKEN_REC_SIZE, + SKIN_TOKEN_REC_PRERECORD, + SKIN_TOKEN_REC_CLIPCOUNT, + SKIN_TOKEN_REC_TRIGGER, + SKIN_TOKEN_REC_WARNING, + SKIN_TOKEN_REC_SOURCE, + SKIN_TOKEN_REC_GAIN, /* Radio Tokens */ diff --git a/manual/appendix/appendix.tex b/manual/appendix/appendix.tex index 0305df4870..0af2ad49c6 100644 --- a/manual/appendix/appendix.tex +++ b/manual/appendix/appendix.tex @@ -38,7 +38,7 @@ files etc. (IMPORTANT!) \section{Feature ideas} To suggest an idea for a feature or to read those made by others, see -\url{https://forums.rockbox.org/index.php?board=49.0}. Please keep in +\url{https://www.rockbox.org/forums/index.php?board=49.0}. Please keep in mind that this forum is for the discussion of feature ideas -- they are not requests and there is no guarantee they will be acted upon. diff --git a/manual/appendix/config_file_options.tex b/manual/appendix/config_file_options.tex index 2b580f3206..b8e0e1f2e7 100644 --- a/manual/appendix/config_file_options.tex +++ b/manual/appendix/config_file_options.tex @@ -36,7 +36,7 @@ repeat & off, all, one, shuffle, ab & N/A\\ play selected & on, off & N/A\\ - single mode & off, track, album, album artist, artist, composer, work, genre + single mode & off, track, album, album artist, artist, composer, work, genre, playlist & N/A\\ party mode & on, off & N/A\\ scan min step & 1, 2, 3, 4, 5, 6, 8, 10, 15, 20, 25, 30, 45, 60 @@ -85,6 +85,9 @@ backlight on button hold & normal, off, on & N/A\\ caption backlight & on, off & N/A\\ brightness & \fixme{devise a way to get ranges from config-*.h} & N/A\\ + \opt{compositevideoout}{ + composite video output & off, auto, on & N/A\\ + } disk spindown & 3 to 254 & s\\ battery capacity & \fixme{devise a way to get ranges from config-*.h} & mAh\\ \opt{HAVE_CAR_ADAPTER_MODE}{ diff --git a/manual/appendix/wps_tags.tex b/manual/appendix/wps_tags.tex index 7c6dbe713a..89e266a877 100644 --- a/manual/appendix/wps_tags.tex +++ b/manual/appendix/wps_tags.tex @@ -319,6 +319,32 @@ Examples: \config{\%?if(\%cs, =, 2)} +\opt{quickscreen}{ +\section{Drawing the Quickscreen using a skin} + +Quickscreen settings can be displayed using the following tags. + +\begin{tagmap} + \config{\%QT} & Top Quickscreen setting (name)\\ + \config{\%Qt} & Top Quickscreen setting (value)\\ + \config{\%QR} & Right Quickscreen setting (name)\\ + \config{\%Qr} & Right Quickscreen setting (value)\\ + \config{\%QB} & Bottom Quickscreen setting (name)\\ + \config{\%Qb} & Bottom Quickscreen setting (value)\\ + \config{\%QL} & Left Quickscreen setting (name)\\ + \config{\%Ql} & Left Quickscreen setting (value)\\ + \end{tagmap} + + Define viewports containing these tags for each Quickscreen position, with your + desired font and formatting. Check if \config{\%cs} is equal to \config{10} + (i.e. Quickscreen is the current screen) before displaying them. + + If \config{\%QT} is present anywhere in the \fname{.sbs}, then the un-skinned + Quickscreen will not be shown. + + \note{If a preset does not match a legal setting, or has no setting applied, all tags will display "ERR".} +} + \section{List Title (\fname{.sbs} only)} \begin{tagmap} \config{\%Lt} & Title text. Should be used in a conditional so that non-list @@ -390,25 +416,6 @@ Examples: \item As a conditional: \config{\%?St(eq enabled)} \end{enumerate} -\opt{quickscreen}{ -\subsection{Displaying Quickscreen settings} - -If your player has a quickscreen, the preset settings can be displayed using the following. - -\begin{tagmap} - \config{\%QT} & Display the name of the top quickscreen setting.\\ - \config{\%Qt} & Display the value of the top quickscreen setting.\\ - \config{\%QR} & Display the name of the right quickscreen setting.\\ - \config{\%Qr} & Display the value of the right quickscreen setting.\\ - \config{\%QB} & Display the name of the bottom quickscreen setting.\\ - \config{\%Qb} & Display the value of the bottom quickscreen setting.\\ - \config{\%QL} & Display the name of the left quickscreen setting.\\ - \config{\%Ql} & Display the value of the left quickscreen setting.\\ - \end{tagmap} - - \note{If a preset does not match a legal setting, or has no setting applied, all tags will display "ERR".} -} - \section{\label{ref:wps_images}Images} \begin{tagmap} \config{\%X(filename.bmp)} @@ -508,6 +515,39 @@ Examples: For general information where to put album art see \reference{ref:album_art}. } +\opt{recording}{ +\section{Recording} +These tags show the state of recording, in any skin. + \begin{tagmap} + \config{\%Rp} & Can this \dap{} record?\\ + \config{\%Rr} & Is a recording in progress?\\ + \config{\%Rh} & Hours of the current recording.\\ + \config{\%Rn} & Minutes of the current recording, 00 to 59.\\ + \config{\%Rs} & Seconds of the current recording, 00 to 59.\\ + \config{\%RS} & Size of the current recording, e.g. ``1.5MB''.\\ + \config{\%RP} & Seconds in the pre-record buffer; empty unless + pre-recording.\\ + \config{\%Re} & Recording format: wav, aiff, wv or mp3. As a conditional: + \config{\%?Re}.\\ + \config{\%Rf} & Sample rate in kHz, e.g. ``44.1''. As a conditional, 1 to + 12: 96, 88.2, 64, 48, 44.1, 32, 24, 22.05, 16, 12, 11.025 and 8~kHz.\\ + \config{\%Rb} & MP3 bitrate in kbps; empty for the other formats. As a + conditional, 1 to 18 for 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, + 144, 160, 192, 224, 256 and 320~kbps.\\ + \config{\%Rm} & Is the recording mono?\\ + \config{\%Ri} & Recording source, by name. As a conditional, the same on + every \dap{}: \config{\%?Ri}.\\ + \config{\%Rg} & Gain of the recording source in dB -- for line in and + FM radio the left channel's. Empty for a source without a gain.\\ + \config{\%Rc} & Number of clips the peak meter has counted.\\ + \config{\%Rt} & Trigger state: off, ready, steady, go, postrec, retrig or + continue. As a conditional: + \config{\%?Rt}.\\ + \config{\%Rw} & Recording warnings, as a hexadecimal number; empty while + there are none.\\ + \end{tagmap} +} + \opt{radio}{ \section{FM Radio} \begin{tagmap} @@ -770,6 +810,13 @@ display cycling round the defined sublines. See specify an image. \item[nobar] -- don't draw the bar or its frame (for use with the ``slider'' option). + \item[hold] -- for the peak meter bars (\config{\%pL}, \config{\%pR}): + the bar falls back at the peak meter's release rate instead of + following each drop, and the highest level is held for the peak hold + time. The next param may be a number, the thickness of the held peak in + pixels (default 1). With a fill image the held peak is drawn from that + part of the image, so for a bar drawn as LED segments set it to the size + of one segment. \opt{touchscreen}{ \item[notouch] -- don't create the touchregion for progress/volume bars. \item[touchlabel] -- the next option sets the label of the bar touch area, for linking with the \%Tl tag. diff --git a/manual/configure_rockbox/display_options.tex b/manual/configure_rockbox/display_options.tex old mode 100755 new mode 100644 index 4b48b768a9..187ed36ba8 --- a/manual/configure_rockbox/display_options.tex +++ b/manual/configure_rockbox/display_options.tex @@ -98,6 +98,13 @@ \item[Brightness.] Changes the brightness of your LCD display. } + \opt{compositevideoout}{ + \item[Composite Video Output.] + Mirrors the LCD and video playback through a supported composite + dock. \setting{Off} disables the output. \setting{Auto} enables it + only when a recognized video dock is connected. \setting{On} also + enables it for other detected dock accessories. + } } % \opt{HAVE_BACKLIGHT} \opt{lcd_contrast}{ @@ -287,4 +294,42 @@ uses a different codepage and you are getting garbled extended characters you should adjust this settings. In most cases sticking to ``ISO-8859-1'' would be sufficient. +% + \opt{touchscreen}{ + \item[Touchscreen Settings.] + Options related to touchscreen behavior. + \begin{description} + \item[Touchscreen Mode.] + Rockbox supports two touchscreen modes. \setting{Absolute Point} behaves + like a normal touchscreen and allows you to select items by tapping on + them directly. \setting{3x3 Grid} divides the touchscreen into thirds to + emulate a grid of 9 buttons: eg. tapping on the center of the screen is + interpreted as a virtual ``center'' button. + \note{Some parts of the UI only support \setting{3x3 Grid} mode and will + fall back to it even if \setting{Absolute Point} mode is selected. In + particular, \setting{3x3 Grid} mode will be used in the WPS unless the + theme supports touch interaction; the default ``Cabbie v2.0'' theme + does not include touch support.} + \item[Touchscreen Flick Shortcuts.] + Enables or disables flick shortcuts in \setting{Absolute Point} mode. + A ``flick'' is any quick swiping motion from the top, bottom, left, or + right edges of the screen towards the center. + \item[Line Padding In Lists.] + This option affects the amount of padding added to list entries, which + makes them larger on screen and easier to select with a touchscreen. + \setting{Auto} will calculate a reasonable amount of padding based on + the standard line height and the screen DPI. \setting{Off} disables the + feature and will not add any padding. Other values allow you to set the + number of pixels of padding to be added to the standard line height. + \item[Calibrate.] + Allows you to change the touchscreen calibration by touching on three + points on the screen to correct for any offset or rotation of the touch + overlay compared to the display. This calibration method is useful for + older devices that use a physically separate touch overlay panels, which + may be slightly mis-aligned to the display. + \item[Reset Calibration.] + Select this option to clear calibration data set by the + \setting{Calibrate} option. + \end{description} + } \end{description} diff --git a/manual/configure_rockbox/playback_options.tex b/manual/configure_rockbox/playback_options.tex index 3287c61739..66a90c6f6b 100644 --- a/manual/configure_rockbox/playback_options.tex +++ b/manual/configure_rockbox/playback_options.tex @@ -88,6 +88,7 @@ you to configure settings related to audio playback. \setting{Composer}, \setting{Work}, or \setting{Genre}, your music will pause at the next track change where this tag also changes. + If it is set to Playlist, your music will pause on the next automatic playlist change. \section{Party Mode} Enables unstoppable music playback. When new songs are diff --git a/manual/configure_rockbox/sound_settings.tex b/manual/configure_rockbox/sound_settings.tex index 7e9d09b75e..50e6177093 100644 --- a/manual/configure_rockbox/sound_settings.tex +++ b/manual/configure_rockbox/sound_settings.tex @@ -315,7 +315,7 @@ change to customise your listening experience. \section{\label{ref:EQ}Equalizer} \screenshot{configure_rockbox/images/ss-equalizer}{The graphical equalizer}{} Rockbox features a parametric equalizer (EQ). In contrast to non-parametric - equalizers, a parametric EQ enables adjusting the center frequency, gain, and + equalizers, a parametric EQ enables adjusting the type, center frequency, gain, and width of EQ bands separately. The ability to adjust the frequency and width of bands enables more precise control of the EQ frequency response while avoiding the use of a large number of bands (often 12+) needed in a @@ -338,10 +338,11 @@ change to customise your listening experience. than are required will waste battery and introduce additional rounding noise. For best results, use the fewest number of bands required.} - Rockbox's parametric EQ is composed of up to ten different bands: + Rockbox's parametric EQ is composed of up to ten EQ bands consisting of any combination of three different filter types: + \begin{description} - \item[Band 0: Low shelf filter.] - The low shelf filter boosts or lowers all frequencies below a certain + \item[Low shelf filter.] + Low shelf filters boost or lower all frequencies below a certain frequency limit, much as the ``bass'' control found on ordinary stereo systems does. Adjust the ``cutoff'' frequency parameter to decide where the shelving @@ -354,12 +355,12 @@ change to customise your listening experience. The ``Q'' parameter should always be set to 0.7 for the shelving filters. Higher values will add a small boost around the cutoff frequency that is almost always undesirable. - \item[Bands 1-8: Peaking filters.] + \item[Peaking filters.] Peaking EQ filters boost or lower a frequency range centered at the centre frequency chosen. Graphic equalizers in home stereos are usually peaking filters. The peaking filters in Rockbox's EQ lets you adjust three - different parameters for EQ bands 1 through 8. The ``centre'' parameter + different parameters. The ``centre'' parameter controls the centre frequency of the frequency range that is affected as described above. The ``gain'' parameter controls how much each band is adjusted, and @@ -368,11 +369,11 @@ change to customise your listening experience. frequency range is. Higher Q values will affect a narrower band of frequencies, while lower Q values will affect a wider band of frequencies. - \item[Band 9: High shelf filter.] + \item[High shelf filter.] A high shelf filter boosts or lowers all frequencies above a certain frequency limit, much as the ``treble'' control found on ordinary stereo systems does. - The high shelf filter is adjusted the same way as the low shelf filter, + A high shelf filter is adjusted the same way as A low shelf filter, except that it works on the high end of the frequency spectrum rather than the low end. \end{description} @@ -385,7 +386,7 @@ change to customise your listening experience. \item[Graphical EQ.] This option brings up a graphic EQ screen, which allows adjustment of each of - the three parameters described above (gain, centre frequency, and Q) for each + the four parameters described above (gain, centre frequency, Q, and type) for each of the five EQ bands. \begin{btnmap} @@ -476,8 +477,8 @@ change to customise your listening experience. \opt{IRIVER_RC_H100_PAD}{\ButtonRCSelect} \opt{GIGABEAT_RC_PAD,IAUDIO_RC_PAD}{\ButtonRCPlay} &} - Toggles the cursor among the three parameters (gain, centre frequency, - Q) for the selected EQ band + Toggles the cursor among the four parameters (type, gain, centre + frequency, Q) for the selected EQ band \\ % \opt{IRIVER_H100_PAD,IRIVER_H300_PAD}{\ButtonMode} diff --git a/manual/getting_started/installation.tex b/manual/getting_started/installation.tex index 5358c101db..dbea5b335f 100644 --- a/manual/getting_started/installation.tex +++ b/manual/getting_started/installation.tex @@ -10,20 +10,30 @@ before beginning there are a few important things to know. follow the instructions at \wikilink{SansaE200RInstallation}.}} -\opt{ipodnano,ipodnano2g,ipodvideo,ipod6g,e200,c200,c200v2,e200v2,clipv1,clipv2,cowond2,fuze,fuzev2}{ +\opt{ipodnano,ipodnano2g,ipodnano3g,ipodvideo,ipod6g,e200,c200,c200v2,e200v2,clipv1,clipv2,cowond2,fuze,fuzev2}{ \begin{description} \item[Supported hardware versions.] - \opt{ipodnano,ipodnano2g}{ + \opt{ipodnano,ipodnano2g,ipodnano3g}{ The \playertype{} is available in multiple versions, not - all of which run Rockbox. Rockbox presently runs only on - the first and second generation Ipod Nano. Rockbox does - \emph{not} run on the third, fourth or fifth generation Ipod Nano. - For information on identifying which Ipod you own, see this page on + all of which run Rockbox. Rockbox presently runs on the first, + second and third generation iPod Nano. Rockbox does + \emph{not} run on the fourth, fifth, sixth or seventh generation + iPod Nano. + For information on identifying which iPod you own, see this page on Apple's website: \url{http://www.info.apple.com/kbnum/n61688}. } + \opt{ipodnano3g}{ + The \playertype{} referred to by this manual is the third generation + model, the short and wide one with a \dapdisplaywidth{}x% + \dapdisplayheight{} screen. It came with 4GB and 8GB of flash storage. + Apple fitted several different NAND chips, and Rockbox so far supports + the models with Hynix NAND, 4GB and 8GB: on any other chip the + bootloader says so and starts Apple's firmware instead. The + installation section below says how to have your player's chip added. + } \opt{ipodvideo}{ The \playertype{} is the 5th/5.5th generation \playerman{} only. - For information on identifying which Ipod you own, see this page on Apple's + For information on identifying which iPod you own, see this page on Apple's website: \url{http://www.info.apple.com/kbnum/n61688}. } \opt{ipod6g}{ @@ -160,7 +170,7 @@ of before installing. \item[Firewire connection.] } To transfer Rockbox to your \dap{} you need to - connect it to your computer.\nopt{rgnano}{For manual installation/uninstallation, or + connect it to your computer. \nopt{rgnano}{For manual installation/uninstallation, or should autodetection fail during automatic installation, you need to know where to access the \dap{}.} On Windows this means you need to know the drive letter associated with the \dap{}. On Linux you need to know @@ -168,10 +178,10 @@ of before installing. name of your \dap{}. \opt{ipod}{ - If you have Itunes installed and it is configured to open automatically + If you have iTunes installed and it is configured to open automatically when your \dap{} is attached (the default behaviour), then wait for it to open and then quit it. You also need to ensure the ``Enable use as disk'' - option is enabled for your \dap{} in Itunes. Your \dap{} should then enter + option is enabled for your \dap{} in iTunes. Your \dap{} should then enter disk mode automatically when connected to a computer via \nopt{ipod1g2g}{USB.}\opt{ipod1g2g}{Firewire.} If your computer does not recognise your \dap{}, you may need to enter disk mode manually. Disconnect @@ -258,14 +268,14 @@ of before installing. \opt{ipod}{ \begin{description} - \item[File system format.] Rockbox only works on Ipods formatted with - the FAT32 filesystem (i.e. Ipods initialised by Itunes - for Windows). It does not work with the HFS+ filesystem (i.e. Ipods - initialised by Itunes for the Mac). More information and instructions for - converting an Ipod to FAT32 can be found on the + \item[File system format.] Rockbox only works on iPods formatted with + the FAT32 filesystem (i.e. iPods initialised by iTunes + for Windows). It does not work with the HFS+ filesystem (i.e. iPods + initialised by iTunes for the Mac). More information and instructions for + converting an iPod to FAT32 can be found on the \wikilink{IpodConversionToFAT32} wiki page on the Rockbox website. Note that after conversion, you can still use - a FAT32 Ipod with a Mac. + a FAT32 iPod with a Mac. \end{description} } @@ -274,26 +284,26 @@ of before installing. } \opt{disk_storage}{ -Rockbox supports very large drive capacities when used with GPT partitioning on devices that utilize ATA storage, but due to the limitations of the FAT32 filesystem, individual paritions cannot exceed 2TiB. Additionally, there may be underlying platform limitations that make it difficult, if not impossible, utilize a drive larger than some arbitrary size or boot from a drive using GPT partitioning. +Rockbox supports very large drive (over 2\,TiB) capacities when used with GPT partitioning on \dap{}s that utilize ATA storage, but due to the limitations of the FAT32 filesystem, individual paritions cannot exceed 2\,TiB. Additionally, there may be underlying platform limitations that make it difficult, if not impossible, utilize a drive larger than some arbitrary size or boot from a drive using GPT partitioning. \opt{ipod6g}{ -For example, the stock Apple firmware on earlier 6th generation iPod Classic models\footnote{MB029, MB145, MB147, MB150} is limited to drives no larger than 128GiB, and booting into the Apple firmware firmware with larger drive sizes will result in massive data corruption. Later 6th generation iPod Classic models\footnote{MB562, MB565, MC293, MC297} (sometimes referred to as ``7th gen'') do not have this issue. Rockbox itself will function correctly on all 6th/7th generation iPod Classic models. +For example, the stock Apple firmware on earlier 6th generation iPod Classic models\footnote{MB029, MB145, MB147, MB150} is limited to drives no larger than 128\,GiB, and booting into the Apple firmware firmware with larger drive sizes will result in massive data corruption. Later 6th generation iPod Classic models\footnote{MB562, MB565, MC293, MC297} (sometimes referred to as ``7th gen'') do not have this issue. Rockbox itself will function correctly on all 6th/7th generation iPod Classic models. } -\note{In practice, no Rockbox-capable device can currently handle total drive capacities exceeding 2TiB.} +\note{In practice, no Rockbox-capable \dap{} can currently handle total drive capacities exceeding 2\,TiB.} } \opt{sd_storage}{ - Rockbox does not currently support SDUC cards, ie those with capacities - exceeding 2TiB. However, this is purely a software limitation with - Rockbox's SD card drivers, and will be addressed in a future release. + Rockbox does not currently support SDUC cards, i.e. those with capacities + exceeding 2\,TiB. However, this is purely a software limitation with + Rockbox's SD controller drivers, and will be addressed in a future release. - \note{SD cards exceeding 32GiB are pre-formated using the exFAT filesystem with GPT paritioning. + \note{SD cards exceeding 32\,GiB are pre-formated using the exFAT filesystem with GPT partitioning. Before they can be used with Rockbox, they must be reformatted with FAT32.} } \opt{disk_storage,sd_storage}{ -\note{The disk format utility included with all versions Microsoft Windows released to date will refuse to create/format FAT32 partitons exceeding 32GiB, so users of larger storage devices will need an alternative tool. Options include GParted Live\footnote{\url{https://gparted.org/}}\opt{ipod}{ or performing a restore using iTunes on Windows}. \opt{sd_storage}{Addtionally, devices that support removable storage typically have a built-in format tool that can be used.}} +\note{The disk format utility included with all versions Microsoft Windows released to date will refuse to create/format FAT32 partitons exceeding 32\,GiB, so users of larger storage devices will need an alternative tool. Options include GParted Live\footnote{\url{https://gparted.org/}}\opt{ipod}{ or performing a restore using iTunes on Windows}. \opt{sd_storage}{Additionally, devices that support removable storage typically have a built-in format tool that can be used.}} } \opt{disk_storage}{ @@ -307,21 +317,21 @@ For example, the stock Apple firmware on earlier 6th generation iPod Classic mod advanced power management and the \emph{removeable} attribute. They are performant, reliable, and physically robust, but tend to be expensive and not available in larger sizes. \item[SATA.] These are fast, reliable, and available in high capacities, but are typically optimized for high performance - at the expense of power consumption.. However, as they implement the full ATA command set, we are able + at the expense of power consumption. However, as they implement the full ATA command set, we are able to minimize their power consumption and power them down when not being actively used. \item[Single Secure Digital (SD).] While these adapters come in different form factors from multiple vendors, they are all based on the same basic design. The ATA command set is incompletely emulated, notably lacking - support for \emph{mandatory} ATA power management commands that Rockbox uses to safely + support for several \emph{mandatory} ATA power management commands that Rockbox uses to safely transition the device in and out of low power states. Additionally, SD cards vary widely in quality - and power consumption with the resultant effects on data longevity and battery life. Finally, these SD adapters - do not support 2TiB or larger SDUC cards. + and power consumption with the resultant effects on data longevity and battery life. Finally, to date, none + of these SD adapters support 2\,TiB or larger SDUC cards. \item[Dual/Quad SD.] These are similar to the above, only allowing use of mulitiple SD cards to - increase the overall storage capacity. While typically described as JBOD\footnote{Just a Bunch Of Disks}, this is not accurate as each card is not individually accessable. Instead, the adapter claims to be to be a single logical drive of the combined capacity of the individual cards in a RAID0-like manner. Consquently, if any one card fails, all data on all other cards may be rendered inacessible. Given the quality concerns mentioned earlier, this means use of multiple SD cards in one of these adapters is the least reliable/robust of the various SSD mods. Finally, in another violation of the ATA specification, these ATA-SD adapters fail to properly support LBA48 addressing, meaning that no matter what combination of cards is used, if their combined capacity exceeds 2TiB, the extra capacity will not be usable, and the device may even present as having (considerably) less space. + increase the overall storage capacity. While typically described as JBOD\footnote{Just a Bunch Of Disks}, this is not accurate as each card is not individually accessable. Instead, the adapter claims to be a single logical drive of the combined capacity of the individual cards in a RAID0-like manner. Consquently, if any one card fails, all data on all other cards may be rendered inacessible. Given the quality concerns mentioned earlier, this means use of multiple SD cards in one of these adapters is the least reliable/robust of the various SSD mods. Finally, in another violation of the ATA specification, these ATA-SD adapters fail to properly support LBA48 addressing, meaning that no matter what combination of cards is used, if their combined capacity exceeds 2\,TiB, the extra capacity will not be usable, and the device may even present as having (considerably) less space. \end{description} \note{All of these flash/SSD mods take up less physical space in the device enclosure than the original hard drive, so care must be taken to ensure they are securely mounted and resistant to the vibration and impacts that typically occur in portable devices. Ribbon cables are particularly vulnerable.} -It is also worth noting that you will not likely see improved data transfer rates when using solid-state storage; the underlying \dap{} hardware is simply too slow to materially benefit. However, the improved access times should help the \dap{} to feel much more responsive. +It is also worth noting that data transfer rates are not likely to be improved when using solid-state storage; the underlying \dap{} hardware is simply too slow to materially benefit. However, the improved access times should help the \dap{} to feel much more responsive. } \section{Installing Rockbox}\label{sec:installing_rockbox}\index{Installation} @@ -349,7 +359,7 @@ It is also worth noting that you will not likely see improved data transfer rate \end{itemize} } -\opt{gigabeats,fiiom3k,shanlingq1}{ +\opt{gigabeats,fiiom3k,shanlingq1,ipodnano3g}{ \note{The automated install is not yet available for the \playerlongtype{}. For now you can use the manual method to install Rockbox. Please still read the section on the automatic install as it explains @@ -560,12 +570,12 @@ that the download name includes "erosqnative"!}} the Rockbox source code repository and represents the current state of Rockbox development. This means that the build could contain bugs but most of the time is safe to use. For a manual install, you can download the current build from - \url{https://build.rockbox.org/}. + \url{https://www.rockbox.org/download/devbuilds.html}. -\item[Archived Build.] In addition to the release version and the current build, +\item[Archived Daily Build.] In addition to the release version and the current build, there is also an archive of daily builds available for download. These are built once a day from the latest source code in the repository. For a manual install, - you can download archived builds from \url{https://www.rockbox.org/daily.shtml}. + you can download archived builds from \url{https://www.rockbox.org/download/daily.shtml}. \end{description} @@ -674,10 +684,12 @@ by Rockbox, in the main directory of your \daps{} drive. \opt{iriverh100,iriverh300}{\input{getting_started/iriver_install.tex}} \opt{mpiohd200,mpiohd300}{\input{getting_started/mpio_install.tex}} \opt{ipod}{ - \nopt{ipod6g} { + \nopt{ipod6g,ipodnano3g} { \input{getting_started/ipod_install.tex}} \opt{ipod6g} { - \input{getting_started/ipod6g_install.tex}} } + \input{getting_started/ipod6g_install.tex}} + \opt{ipodnano3g} { + \input{getting_started/ipodnano3g_install.tex}} } \opt{iaudio}{\input{getting_started/iaudio_install.tex}} \opt{iriverh10,iriverh10_5gb}{\input{getting_started/h10_install.tex}} \opt{gigabeatf}{\input{getting_started/gigabeat_install.tex}} @@ -809,7 +821,7 @@ to speak file and folder names. \section{Running Rockbox} \nopt{ipod,gigabeats,cowond2,rgnano}{When you turn the unit on, Rockbox should load.} -\opt{ipod}{Hard reset the Ipod by holding +\opt{ipod}{Hard reset the iPod by holding \opt{IPOD_4G_PAD}{\ButtonMenu{} and \ButtonSelect{} simultaneously}% \opt{IPOD_3G_PAD}{\ButtonMenu{} and \ButtonPlay{} simultaneously} for a couple of seconds until the \dap{} resets. Now Rockbox should load. @@ -837,11 +849,11 @@ you turn the unit on, Rockbox should load.} \opt{ipod}{ \note{ - If you have loaded music onto your \dap{} using Itunes, + If you have loaded music onto your \dap{} using iTunes, you will not be able to see your music properly in the \setting{File Browser}. - This is because Itunes changes your files' names and hides them in - directories in the \fname{Ipod\_Control} directory. Files placed on your - \dap{} using Itunes can be viewed by initialising and using Rockbox's database. + This is because iTunes changes your files' names and hides them in + directories in the \fname{iPod\_Control} directory. Files placed on your + \dap{} using iTunes can be viewed by initialising and using Rockbox's database. See \reference{ref:database} for more information. } } @@ -934,10 +946,16 @@ completely follow the manual uninstallation instructions below.}} } \optv{ipod}{ - To uninstall Rockbox and go back to using just the original Ipod software, connect + \nopt{ipodnano3g}{ + To uninstall Rockbox and go back to using just the original iPod software, connect the \dap{} to your computer and follow the instructions to install the bootloader but, when prompted by ipodpatcher, enter \texttt{u} for uninstall instead of \texttt{i} for install. + } + \opt{ipodnano3g}{ + Follow the DFU-mode steps above, then run + \fname{mks5lboot --bl-uninst ipodnano3g} to remove the bootloader. + } } \opt{iaudiom3,iaudiom5,iaudiox5}{ diff --git a/manual/getting_started/ipodnano3g_install.tex b/manual/getting_started/ipodnano3g_install.tex new file mode 100644 index 0000000000..3f4447b233 --- /dev/null +++ b/manual/getting_started/ipodnano3g_install.tex @@ -0,0 +1,40 @@ +The third generation iPod Nano uses a DFU installer, \fname{mks5lboot}, +to install the bootloader in flash. Do not use \fname{ipodpatcher}. + +\warn{Rockbox supports the Nano 3G models fitted with Hynix NAND, the +4~GB A514D3AD and the 8~GB A555D5AD. Apple fitted several other NAND +chips to this player. Rockbox drives only chips that have been tested on +hardware: on any other chip the bootloader says the NAND is not supported +and starts Apple's firmware instead, leaving the player's storage +untouched. The port is experimental: Apple's proactive +refresh of blocks needing ECC correction is not implemented, and +bad-block remapping has not been exercised on hardware. Keep a backup of +all media.} + +If your player has another chip, you can help have it added. The NAND check in \fname{utils/ipodnano3g/nandcheck} of the +source tree runs from DFU mode, installs nothing, writes nothing to the +player, and collects what validating the chip needs: see that +directory's \fname{README.md}. + +\caps{Rockbox Utility} can install the bootloader. To install it by hand +instead, follow the steps below. + +Use \fname{mks5lboot} built from Rockbox sources +with Nano 3G support, and the Nano 3G bootloader +\fname{bootloader-ipodnano3g.ipod}. Older iPod Classic installers do not +support this player. Build instructions and USB driver requirements are +in \fname{utils/mks5lboot/README} in the source tree. + +\begin{enumerate} +\item Install the Rockbox files on the player's FAT32 volume before +installing the bootloader. +\item Connect the player by USB. Close iTunes and stop iTunesHelper if +they are running. +\item Run \fname{mks5lboot --dfuscan --loop} in a terminal, then put the +player in +\href{https://freemyipod.org/wiki/Modes#DFU_mode}{DFU mode}. +When it is detected, stop the scan with Ctrl+C. +\item Run \fname{mks5lboot --bl-inst path/to/bootloader-ipodnano3g.ipod}, +replacing the path with the location of your Nano 3G bootloader. +\item Wait for the installer to finish and the player to restart. +\end{enumerate} diff --git a/manual/getting_started/main.tex b/manual/getting_started/main.tex index 825fa1ab49..29323de734 100644 --- a/manual/getting_started/main.tex +++ b/manual/getting_started/main.tex @@ -33,7 +33,7 @@ of call when looking for further help. If you cannot find the information you are searching for on the Rockbox website there are a number of support channels you should have a look at. -You can try the Rockbox forums located at \url{https://forums.rockbox.org/}. +You can try the Rockbox forums located at \url{https://www.rockbox.org/forums/}. The mailing lists are another option, and can be found at \url{https://www.rockbox.org/mail/}. From that page you can subscribe to the lists and browse the archives. To search the list archives simply use diff --git a/manual/intro/main.tex b/manual/intro/main.tex index 5f73076fec..ec3f16e4e1 100644 --- a/manual/intro/main.tex +++ b/manual/intro/main.tex @@ -39,7 +39,7 @@ Rockbox and this manual is the collaborative effort of the Rockbox team and its contributors. See the appendix for a complete list of contributors. -\copyright~2003--2025 The Rockbox Team and its contributors, +\copyright~2003--2026 The Rockbox Team and its contributors, \copyright~2004 Christi Alice Scarborough, \copyright~2003 Jos\'{e} Maria Garcia-Valdecasas Bernal \& Peter Schlenker. \end{quote} diff --git a/manual/platform/ipod1g2g.tex b/manual/platform/ipod1g2g.tex index eeed6c4e3a..df6ca63e40 100644 --- a/manual/platform/ipod1g2g.tex +++ b/manual/platform/ipod1g2g.tex @@ -5,7 +5,7 @@ \edef\UseOption{\UseOption,HAVE_DISK_STORAGE} \edef\UseOption{\UseOption,ipod} -\newcommand{\playerman}{Ipod} +\newcommand{\playerman}{iPod} \newcommand{\playertype}{1G / 2G} \newcommand{\playerlongtype}{\playertype} \newcommand{\dapdisplaywidth}{160} diff --git a/manual/platform/ipod3g.tex b/manual/platform/ipod3g.tex index bd4fffd21b..ff694a0a95 100644 --- a/manual/platform/ipod3g.tex +++ b/manual/platform/ipod3g.tex @@ -5,7 +5,7 @@ \edef\UseOption{\UseOption,HAVE_DISK_STORAGE} \edef\UseOption{\UseOption,ipod} -\newcommand{\playerman}{Ipod} +\newcommand{\playerman}{iPod} \newcommand{\playertype}{3G} \newcommand{\playerlongtype}{\playertype} \newcommand{\dapdisplaywidth}{160} diff --git a/manual/platform/ipod4g.tex b/manual/platform/ipod4g.tex index 501a508109..226ae870bc 100644 --- a/manual/platform/ipod4g.tex +++ b/manual/platform/ipod4g.tex @@ -5,7 +5,7 @@ \edef\UseOption{\UseOption,HAVE_DISK_STORAGE} \edef\UseOption{\UseOption,ipod} -\newcommand{\playerman}{Ipod} +\newcommand{\playerman}{iPod} \newcommand{\playertype}{4G} \newcommand{\playerlongtype}{\playertype} \newcommand{\dapdisplaywidth}{160} diff --git a/manual/platform/ipod6g.tex b/manual/platform/ipod6g.tex index 96324c253a..3feb381c65 100644 --- a/manual/platform/ipod6g.tex +++ b/manual/platform/ipod6g.tex @@ -4,9 +4,11 @@ \edef\UseOption{\UseOption,HAVE_BACKLIGHT} \edef\UseOption{\UseOption,HAVE_DISK_STORAGE} \edef\UseOption{\UseOption,HAVE_CAR_ADAPTER_MODE} +\edef\UseOption{\UseOption,HAVE_COMPOSITE_VIDEO_OUT} +\edef\UseOption{\UseOption,HAVE_HW_H264} \edef\UseOption{\UseOption,ipod} -\newcommand{\playerman}{Ipod} +\newcommand{\playerman}{iPod} \newcommand{\playertype}{Classic} \newcommand{\playerlongtype}{\playertype} \newcommand{\dapdisplaywidth}{320} diff --git a/manual/platform/ipodcolor.tex b/manual/platform/ipodcolor.tex index 1264ad4298..517d4fb82d 100644 --- a/manual/platform/ipodcolor.tex +++ b/manual/platform/ipodcolor.tex @@ -5,7 +5,7 @@ \edef\UseOption{\UseOption,HAVE_DISK_STORAGE} \edef\UseOption{\UseOption,ipod} -\newcommand{\playerman}{Ipod} +\newcommand{\playerman}{iPod} \newcommand{\playertype}{Color} \newcommand{\playerlongtype}{\playertype} \newcommand{\dapdisplaywidth}{220} diff --git a/manual/platform/ipodmini1g.tex b/manual/platform/ipodmini1g.tex index c884ab16de..ec5a580360 100644 --- a/manual/platform/ipodmini1g.tex +++ b/manual/platform/ipodmini1g.tex @@ -5,7 +5,7 @@ \edef\UseOption{\UseOption,HAVE_DISK_STORAGE} \edef\UseOption{\UseOption,ipod} -\newcommand{\playerman}{Ipod} +\newcommand{\playerman}{iPod} \newcommand{\playertype}{Mini} \newcommand{\playerlongtype}{\playertype} \newcommand{\dapdisplaywidth}{138} diff --git a/manual/platform/ipodnano1g.tex b/manual/platform/ipodnano1g.tex index f1f6ab2735..eb33908325 100644 --- a/manual/platform/ipodnano1g.tex +++ b/manual/platform/ipodnano1g.tex @@ -4,7 +4,7 @@ \edef\UseOption{\UseOption,HAVE_BACKLIGHT} \edef\UseOption{\UseOption,ipod} -\newcommand{\playerman}{Ipod} +\newcommand{\playerman}{iPod} \newcommand{\playertype}{Nano} \newcommand{\playerlongtype}{\playertype{} 1st generation} \newcommand{\dapdisplaywidth}{176} diff --git a/manual/platform/ipodnano2g.tex b/manual/platform/ipodnano2g.tex index a807f5652f..09ad1cafc3 100644 --- a/manual/platform/ipodnano2g.tex +++ b/manual/platform/ipodnano2g.tex @@ -4,7 +4,7 @@ \edef\UseOption{\UseOption,HAVE_BACKLIGHT} \edef\UseOption{\UseOption,ipod} -\newcommand{\playerman}{Ipod} +\newcommand{\playerman}{iPod} \newcommand{\playertype}{Nano} \newcommand{\playerlongtype}{\playertype{} 2nd generation} \newcommand{\dapdisplaywidth}{176} diff --git a/manual/platform/ipodnano3g.tex b/manual/platform/ipodnano3g.tex new file mode 100644 index 0000000000..b273eb5e75 --- /dev/null +++ b/manual/platform/ipodnano3g.tex @@ -0,0 +1,24 @@ +\def\UseOption{ipodnano3g} +\edef\UseOption{\UseOption,HAVE_RB_BL_IN_FLASH} +\edef\UseOption{\UseOption,IPOD_4G_PAD} +\edef\UseOption{\UseOption,HAVE_BACKLIGHT} +\edef\UseOption{\UseOption,ipod} + +\newcommand{\playerman}{iPod} +\newcommand{\playertype}{Nano} +\newcommand{\playerlongtype}{\playertype{} 3rd generation} +\newcommand{\dapdisplaywidth}{320} +\newcommand{\dapdisplayheight}{240} +\newcommand{\dapdisplaydepth}{16} +\newcommand{\specimg}{ipodnano3g} +%Used to name the player, e.g. ...to the \dap +\newcommand{\dap}{player} +%For use when referring to the player. E.g. the \daps\ capacity ... +\newcommand{\daps}{player's} +\newcommand{\firmwarefilename}{\fname{rockbox.ipod}} +\newcommand{\firmwareextension}{\fname{ipod}} +\newcommand{\screenshotsize}{8cm} +\newcommand{\disk}{flash storage} + +% link external keymap file +\input{platform/keymap-ipod4g.tex} diff --git a/manual/platform/ipodvideo.tex b/manual/platform/ipodvideo.tex index 9699a20c55..5368367925 100644 --- a/manual/platform/ipodvideo.tex +++ b/manual/platform/ipodvideo.tex @@ -6,7 +6,7 @@ \edef\UseOption{\UseOption,HAVE_CAR_ADAPTER_MODE} \edef\UseOption{\UseOption,ipod} -\newcommand{\playerman}{Ipod} +\newcommand{\playerman}{iPod} \newcommand{\playertype}{Video} \newcommand{\playerlongtype}{\playertype} \newcommand{\dapdisplaywidth}{320} diff --git a/manual/plugins/h264_player.tex b/manual/plugins/h264_player.tex new file mode 100644 index 0000000000..fb530bd309 --- /dev/null +++ b/manual/plugins/h264_player.tex @@ -0,0 +1,26 @@ +\subsection{H.264 Player} + +The H.264 Player uses a supported player's hardware video decoder to play +H.264 video from \fname{.mp4}, \fname{.m4v}, and \fname{.mov} files. It +supports H.264 Baseline Profile through level 3.0 at resolutions up to +640x480. AAC-LC audio is supported. + +Select a supported file in the Rockbox \setting{File Browser} to start +playback. Playback resumes from the last saved position unless the previous +playback reached the end or the final five percent of the file. + +\begin{btnmap} + \ActionWpsPlay + & Pause / Resume\\ + \ActionWpsVolUp{} / \ActionWpsVolDown + & Adjust volume up / down\\ + \ActionWpsSkipPrev{} / \ActionWpsSkipNext + & Seek backward / forward by 10 seconds\\ + \ActionWpsMenu + & Open the H.264 Player menu\\ + \ActionWpsStop + & Exit the player\\ +\end{btnmap} + +The player menu can resume playback or exit the player. A resume point is +saved when playback is stopped before the final five percent of the file. diff --git a/manual/plugins/images/ss-xrick-220x176x16.png b/manual/plugins/images/ss-xrick-220x176x16.png old mode 100755 new mode 100644 diff --git a/manual/plugins/images/ss-xrick-240x320x16.png b/manual/plugins/images/ss-xrick-240x320x16.png old mode 100755 new mode 100644 diff --git a/manual/plugins/images/ss-xrick-320x240x16.png b/manual/plugins/images/ss-xrick-320x240x16.png old mode 100755 new mode 100644 diff --git a/manual/plugins/main.tex b/manual/plugins/main.tex index 1da6c16b59..dd07a4eba1 100644 --- a/manual/plugins/main.tex +++ b/manual/plugins/main.tex @@ -182,6 +182,9 @@ option from the \setting{Context Menu} (see \reference{ref:Contextmenu}).} & \fname{.s3m, .stm, .stx, .ult,} & \\ & \fname{.uni, .umx, .xm} & \\ MPEG Player & \fname{.mpg, .mpeg, .mpv, .m2v} & \\ + } + \opt{HAVE_HW_H264}{ + H.264 Player & \fname{.mp4, .m4v, .mov} & \\ } MP3 Encoder & \fname{.wav} & x \\ Rockboy & \fname{.gb, .gbc} & \\ @@ -211,6 +214,8 @@ option from the \setting{Context Menu} (see \reference{ref:Contextmenu}).} \nopt{lowmem}{\input{plugins/mpegplayer.tex}} +\opt{HAVE_HW_H264}{\input{plugins/h264_player.tex}} + \input{plugins/mp3_encoder.tex} \opt{iriverh300,iriverh100,SANSA_FUZE_PAD,SANSA_E200_PAD,IPOD_4G_PAD,IPOD_3G_PAD% diff --git a/manual/rockbox_interface/browsing_and_playing.tex b/manual/rockbox_interface/browsing_and_playing.tex index ddf632d379..4ea4d65000 100644 --- a/manual/rockbox_interface/browsing_and_playing.tex +++ b/manual/rockbox_interface/browsing_and_playing.tex @@ -211,6 +211,16 @@ The virtual keyboard can be easily changed by making a text file with the required layout. More information on how to achieve this can be found on the Rockbox website at \wikilink{LoadableKeyboardLayouts}. +\opt{kbd_point_mode}{ + On this player the keyboard is laid out for direct touch where the keys are sized + for a fingertip and are tapped individually, rather than being selected + with the 3\,\texttimes\,3 grid used on other touchscreen targets. By default the + characters are spread over three pages with lower case and accented + letters, their upper-case forms in the same positions, and a third page + of punctuation and additional European characters. Tap + \textbf{\textgreater} below the keys to change page. +} + \opt{morse_input}{ Also you can switch to Morse code input mode by changing the \setting{Use Morse Code Input} setting% diff --git a/manual/rockbox_interface/images/ss-virtual-keyboard-480x720x16.png b/manual/rockbox_interface/images/ss-virtual-keyboard-480x720x16.png new file mode 100644 index 0000000000..9dec07971a Binary files /dev/null and b/manual/rockbox_interface/images/ss-virtual-keyboard-480x720x16.png differ diff --git a/manual/rockbox_interface/images/ss-virtual-keyboard-480x800x16.png b/manual/rockbox_interface/images/ss-virtual-keyboard-480x800x16.png new file mode 100644 index 0000000000..02dd6f710f Binary files /dev/null and b/manual/rockbox_interface/images/ss-virtual-keyboard-480x800x16.png differ diff --git a/manual/rockbox_interface/main.tex b/manual/rockbox_interface/main.tex index 5b5c418792..8d6f4ebd1a 100644 --- a/manual/rockbox_interface/main.tex +++ b/manual/rockbox_interface/main.tex @@ -176,6 +176,14 @@ detail in the following paragraph. \ButtonHold{} is off before trying to use your player. } % + \opt{ipod6g}{ + Earphones with an Apple-compatible inline remote are supported (not on + the 2007 model). A click of the remote's center button toggles + play/pause on any screen, even while the \ButtonHold{} switch is on; + the center button has no long-press function. The remote's volume + buttons adjust the \dap{}'s volume everywhere. + } + % \opt{ipod3g}{ The main controls on the \dap{} are a slightly indented touch wheel with a flat round button in the center, and four buttons in a row above the @@ -622,7 +630,7 @@ To turn on and off your Rockbox enabled \dap{} use the following keys: { \subsection{Starting the original firmware} \label{ref:Dualboot} - \opt{ipod4g,ipodcolor,ipodvideo,ipodnano,ipodnano2g,ipodmini} + \opt{ipod4g,ipodcolor,ipodvideo,ipodnano,ipodnano2g,ipodnano3g,ipodmini} { Rockbox has a dual-boot feature. To boot into the original firmware, shut down the device as described above. Turn on the \ButtonHold{} switch diff --git a/packaging/ctru/ctru.make b/packaging/3ds/3ds.make similarity index 93% rename from packaging/ctru/ctru.make rename to packaging/3ds/3ds.make index 838ff12b90..744bae8747 100644 --- a/packaging/ctru/ctru.make +++ b/packaging/3ds/3ds.make @@ -31,13 +31,13 @@ VERSION_MICRO := 0 APP_TITLE := rockbox APP_DESCRIPTION := Open Source Jukebox Firmware APP_AUTHOR := rockbox.org -APP_ICON := $(ROOTDIR)/packaging/ctru/res/icon.png +APP_ICON := $(ROOTDIR)/packaging/3ds/res/icon.png # CIA -BANNER_AUDIO := $(ROOTDIR)/packaging/ctru/res/banner.wav -BANNER_IMAGE := $(ROOTDIR)/packaging/ctru/res/banner.cgfx -RSF_PATH := $(ROOTDIR)/packaging/ctru/res/app.rsf -#LOGO := $(ROOTDIR)/packaging/ctru/logo.lz11 +BANNER_AUDIO := $(ROOTDIR)/packaging/3ds/res/banner.wav +BANNER_IMAGE := $(ROOTDIR)/packaging/3ds/res/banner.cgfx +RSF_PATH := $(ROOTDIR)/packaging/3ds/res/app.rsf +#LOGO := $(ROOTDIR)/packaging/3ds/logo.lz11 UNIQUE_ID := 0xCB001 PRODUCT_CODE := CTR-ROCKBOX ICON_FLAGS := nosavebackups,visible diff --git a/packaging/ctru/res/app.rsf b/packaging/3ds/res/app.rsf similarity index 100% rename from packaging/ctru/res/app.rsf rename to packaging/3ds/res/app.rsf diff --git a/packaging/ctru/res/banner.cgfx b/packaging/3ds/res/banner.cgfx similarity index 100% rename from packaging/ctru/res/banner.cgfx rename to packaging/3ds/res/banner.cgfx diff --git a/packaging/ctru/res/banner.png b/packaging/3ds/res/banner.png similarity index 100% rename from packaging/ctru/res/banner.png rename to packaging/3ds/res/banner.png diff --git a/packaging/ctru/res/banner.wav b/packaging/3ds/res/banner.wav similarity index 100% rename from packaging/ctru/res/banner.wav rename to packaging/3ds/res/banner.wav diff --git a/packaging/ctru/res/banner_.cgfx b/packaging/3ds/res/banner_.cgfx similarity index 100% rename from packaging/ctru/res/banner_.cgfx rename to packaging/3ds/res/banner_.cgfx diff --git a/packaging/ctru/res/icon.png b/packaging/3ds/res/icon.png similarity index 100% rename from packaging/ctru/res/icon.png rename to packaging/3ds/res/icon.png diff --git a/packaging/ctru/res/icon_yellow.png b/packaging/3ds/res/icon_yellow.png similarity index 100% rename from packaging/ctru/res/icon_yellow.png rename to packaging/3ds/res/icon_yellow.png diff --git a/packaging/ctru/rockbox.xml b/packaging/3ds/rockbox.xml similarity index 100% rename from packaging/ctru/rockbox.xml rename to packaging/3ds/rockbox.xml diff --git a/tools/builds.pm b/tools/builds.pm index 0aafee756f..d2e17fa154 100644 --- a/tools/builds.pm +++ b/tools/builds.pm @@ -235,6 +235,10 @@ $releasenotes="/wiki/ReleaseNotes400"; name => 'Rockchip rk27xx', status => 1, }, + 'samsungypcp3' => { + name => 'Samsung YP-CP3', + status => 1, + }, 'samsungyh820' => { name => 'Samsung YH-820', status => 3, @@ -471,6 +475,10 @@ $releasenotes="/wiki/ReleaseNotes400"; name => 'Hiby R3Pro II', status => 2, }, + 'hidizsap80max' => { + name => 'Hidizs AP80 Pro Max', + status => 2, + }, ); sub manualname { diff --git a/tools/buildzip.pl b/tools/buildzip.pl index e10396dac7..38b7c870b9 100755 --- a/tools/buildzip.pl +++ b/tools/buildzip.pl @@ -222,7 +222,7 @@ sub make_install { foreach my $_dir (@files) { my $dir = "wps/" . $_dir; - if ( -d "$src/$dir" && $_dir !~ /\.\.?/) { + if ( -d "$src/$dir" && $_dir !~ /^\.\.?$/) { unless (glob_mkdir("$userdir/$dir")) { return 0; } @@ -259,7 +259,7 @@ GetOptions ( 'r|root=s' => \$ROOT, # Thus we use the ":" for those for which we use a default value in case of "" # and assign the default value afterwards if ($ziptool eq '') { - $ziptool = "zip -r9"; + $ziptool = "zip -r9 -X"; } if ($output eq '') { $output = "rockbox.zip" @@ -424,7 +424,7 @@ sub buildzip { glob_copy("$ROOT/firmware/drivers/libertas/firmware/*", "$temp_dir/libertas/"); } # add hbmenu shortcut's icon and 3dsx executable - if ($modelname =~ /ctru/) { + if ($modelname =~ /3ds/) { glob_copy("icon.icn", "$temp_dir/"); glob_copy("rockbox.3dsx", "$temp_dir/"); } @@ -668,12 +668,11 @@ sub buildzip { } else { copy("$ROOT/wps/classic_statusbar.mono.sbs", "$temp_dir/wps/classic_statusbar.sbs"); } - if ($remote_depth != $depth) { - copy("$ROOT/wps/classic_statusbar.mono.sbs", "$temp_dir/wps/classic_statusbar.rsbs"); - } else { + if ($remote_depth == $depth) { copy("$temp_dir/wps/classic_statusbar.sbs", "$temp_dir/wps/classic_statusbar.rsbs"); + } elsif ($remote_depth != 0) { + copy("$ROOT/wps/classic_statusbar.mono.sbs", "$temp_dir/wps/classic_statusbar.rsbs"); } - copy("$temp_dir/wps/rockbox_none.sbs", "$temp_dir/wps/rockbox_none.rsbs"); # and the info file copy("rockbox-info.txt", "$temp_dir/rockbox-info.txt"); @@ -692,6 +691,13 @@ sub buildzip { # copy the .lua files glob_mkdir("$temp_dir/rocks/viewers/lua/"); glob_copy('apps/plugins/lua/*.lua', "$temp_dir/rocks/viewers/lua/"); + + # finally, bundle map files for debug + glob_mkdir("$temp_dir/debug"); + glob_copy("*.map", "$temp_dir/debug"); +# codecs and plugin maps add up +# find(find_copyfile(qr/\.(map)/, abs_path("$temp_dir/debug")), 'lib'); +# find(find_copyfile(qr/\.(map)/, abs_path("$temp_dir/debug")), 'apps/plugins'); } my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = @@ -748,12 +754,12 @@ sub runone { } # add hbmenu shortcut and cia file to zip file - if ($modelname =~ /ctru/) { + if ($modelname =~ /3ds/) { copy("rockbox.cia", "3ds"); - copy("$ROOT/packaging/ctru/rockbox.xml", "3ds"); + copy("$ROOT/packaging/3ds/rockbox.xml", "3ds"); system("$ziptool -u $output 3ds/rockbox.xml $target >/dev/null"); - print "$ziptool $output $ROOT/packaging/ctru/rockbox.xml $target >/dev/null\n" if $verbose; + print "$ziptool $output $ROOT/packaging/3ds/rockbox.xml $target >/dev/null\n" if $verbose; system("$ziptool -u $output 3ds/rockbox.cia $target >/dev/null"); print "$ziptool $output rockbox.cia $target >/dev/null\n" if $verbose; } diff --git a/tools/checkwps/checkwps.c b/tools/checkwps/checkwps.c index f46e57173d..a80c081d56 100644 --- a/tools/checkwps/checkwps.c +++ b/tools/checkwps/checkwps.c @@ -33,6 +33,7 @@ #include "viewport.h" #include "file.h" #include "font.h" +#include "misc.h" bool debug_wps = false; int wps_verbose_level = 0; @@ -226,43 +227,127 @@ bool radio_hardware_present(void) #endif #include "fontbundle.h" +#include "themeassets.h" static int loaded_fonts = 0; static struct font _font; -int font_load(const char *path) + +/* mode 0 is for fonts in the WPS + mode 1 is for fonts in the cfg file + mode 2 is everything else in the cfg file + + search is an optional location to search for relative paths +*/ +static int checkwps_loader(const char *path, const char *search, int mode) { + if (!path) + return -1; + if (!path[0]) + return mode? 0 : -2; /* Empty is ok for non-WPS */ + if (path[0] == '-' && !path[1]) + return mode? 0 : -3; /* Also ok for non-WPS */ + if (path[0] == '/') + path++; /* Absolute path, skip leading / */ + /* First see if it exists in the theme */ int fd = open(path, O_RDONLY); - if (fd < 0) { + if (fd >= 0) + goto done; + + { char buf[1024]; - sprintf(buf, ".rockbox/%s", path); - fd = open(buf, O_RDONLY); - if (fd < 0) { + if (!mode) { + sprintf(buf, ".rockbox/%s", path); + fd = open(buf, O_RDONLY); + if (fd >= 0) + goto done; + } + + if (search) + { + sprintf(buf, ".rockbox/%s/%s", search, path); + fd = open(buf, O_RDONLY); + if (fd >= 0) + goto done; + } + + if (mode == 0 || mode == 1) /* not found and it's a font */ + { char *first = strrchr(buf, '/'); char *final = strrchr(buf, '.'); - *final = 0; int missing = 1; - /* Check if font is included in the bundle */ - for (int i = 0 ; bundledfonts[i] != NULL ; i++) { - if (!strcmp(first+1, bundledfonts[i])) { + if (final) *final = 0; + + /* Check if font is a default theme asset */ + for (int i = 0 ; themeassets[i] != NULL; i++) + { + if (first && !strcmp(first+1, themeassets[i])) + { missing = 0; break; } } - if (missing) { - //printf("Font missing >%s<\n", first+1); - return -1; - } else { + + /* Check if font is included in the bundle */ + if (missing) + { + for (int i = 0 ; bundledfonts[i] != NULL ; i++) + { + if (first && !strcmp(first+1, bundledfonts[i])) + { + missing = 2; + break; + } + } + } + + if (missing == 1) + { + printf("Font missing >%s<\n", first+1); + return -4; + } else if (missing == 2) { printf("INFO: Theme requires rockbox font bundle\n"); } } + else if (mode == 2) /* Not found and it's an asset */ + { + char *first = strrchr(buf, '/'); + char *final = strrchr(buf, '.'); + int missing = 1; + if (final) *final = 0; + + for (int i = 0 ; themeassets[i] != NULL; i++) + { + if (first && !strcmp(first+1, themeassets[i])) + { + missing = 0; + break; + } + } + if (missing) + { + printf("ERROR: File missing: %s\n", path); + return -5; + } + } } + +done: if (fd >= 0) close(fd); - int id = 2 + loaded_fonts; + return 0; +} + +int font_load(const char *path) +{ + int rval = checkwps_loader(path, FONT_DIR, 0); + if (rval) + return rval; + + rval = 2 + loaded_fonts; loaded_fonts++; - return id; + return rval; } void font_unload(int font_id) @@ -275,9 +360,174 @@ struct font* font_get(int font) return &_font; } +bool settings_load_config(const char* file, bool apply) +{ + int fd; + char line[128]; + bool theme_changed = false; + fd = open_utf8(file, O_RDONLY); + if (fd < 0) + return false; + + while (read_line(fd, line, sizeof line) > 0) + { + char *name, *value; + if (!settings_parseline(line, &name, &value)) + continue; + if (!strcmp(name, "font")) + { + if (checkwps_loader(value, FONT_DIR, 1)) + return false; + } +#ifdef HAVE_REMOTE_LCD + if (!strcmp(name, "remote font")) + { + if (checkwps_loader(value, FONT_DIR, 1)) + return false; + } + else +#endif +#if LCD_DEPTH > 1 + if (!strcmp(name, "backdrop")) + { + if (checkwps_loader(value, NULL, 2)) /* No default path */ + return false; + } + else +#endif + if (!strcmp(name, "wps")) + { + if (checkwps_loader(value, WPS_DIR, 2)) + return false; + } + else if (!strcmp(name, "sbs")) + { + if (checkwps_loader(value, SBS_DIR, 2)) + return false; + } + else if (!strcmp(name, "fms")) + { + if (checkwps_loader(value, SBS_DIR, 2)) + return false; + } + else if (!strcmp(name, "rwps")) + { + if (checkwps_loader(value, WPS_DIR, 2)) + return false; + } + else if (!strcmp(name, "rsbs")) + { + if (checkwps_loader(value, SBS_DIR, 2)) + return false; + } + else if (!strcmp(name, "rfms")) + { + if (checkwps_loader(value, SBS_DIR, 2)) + return false; + } + else if (!strcmp(name, "iconset")) + { + if (checkwps_loader(value, ICON_DIR, 2)) + return false; + } + else if (!strcmp(name, "viewers iconset")) + { + if (checkwps_loader(value, ICON_DIR, 2)) + return false; + } +#ifdef HAVE_REMOTE_LCD + else if (!strcmp(name, "remote iconset")) + { + if (checkwps_loader(value, ICON_DIR, 2)) + return false; + } +#endif +#ifdef HAVE_LCD_COLOR + else if (!strcmp(name, "filetype colours")) + { + if (checkwps_loader(value, THEME_DIR, 2)) + return false; + } +#endif + + if (!string_to_cfg(name, value, &theme_changed)) + { + if (strcmp(name, "fms") && strcmp(name, "rfms")) { + /* Unknown settings are ignored by firmware */ + printf("WARNING: Invalid setting: %s = %s\n", name, value); + continue; + } + } + } /* while(...) */ + + close(fd); + + (void)apply; + return true; +} + /* This is no longer defined in ROCKBOX builds so just use a huge value */ #define SKIN_BUFFER_SIZE (200*1024) +int check_filetype(const char *ext, enum skinnable_screens *skin, + enum screen_type *screen) +{ + if (!strcmp(ext, "sbs")) + { + *skin = CUSTOM_STATUSBAR; + *screen = SCREEN_MAIN; + } + else if (!strcmp(ext, "wps")) + { + *skin = WPS; + *screen = SCREEN_MAIN; + } + else if (!strcmp(ext, "fms")) + { +#if CONFIG_TUNER + *skin = FM_SCREEN; + *screen = SCREEN_MAIN; +#else + return 1; +#endif + } + else if (!strcmp(ext, "rsbs")) + { +#ifdef HAVE_REMOTE_LCD + *skin = CUSTOM_STATUSBAR; + *screen = SCREEN_REMOTE; +#else + return 1; /* unsupported, but not an error */ +#endif + } + else if (!strcmp(ext, "rwps")) + { +#ifdef HAVE_REMOTE_LCD + *skin = WPS; + *screen = SCREEN_REMOTE; +#else + return 1; +#endif + } + else if (!strcmp(ext, "rfms")) + { +#if defined(HAVE_REMOTE_LCD) && CONFIG_TUNER + *skin = FM_SCREEN; + *screen = SCREEN_REMOTE; +#else + return 1; +#endif + } + else if (!strcmp(ext, "cfg")) + { + return 2; + } + else + return -1; + + return 0; +} + int main(int argc, char **argv) { int ret = 0; @@ -286,7 +536,7 @@ int main(int argc, char **argv) struct wps_data wps={0}; enum screen_type screen = SCREEN_MAIN; - struct screen* wps_screen; + enum skinnable_screens skin; /* No arguments -> print the help text * Also print the help text upon -h or --help */ @@ -294,7 +544,8 @@ int main(int argc, char **argv) strcmp(argv[1],"-h") == 0 || strcmp(argv[1],"--help") == 0 ) { - printf("Usage: checkwps [OPTIONS] filename.wps [filename2.wps]...\n"); + printf("Usage: checkwps [OPTIONS] [filename1] [filename2] ...\n"); + printf(" supported file types: wps, sbs, fms, cfg, rwps, rsbs, rfms\n"); printf("\nOPTIONS:\n"); printf("\t-v\t\tverbose\n"); printf("\t-vv\t\tmore verbose\n"); @@ -335,37 +586,38 @@ int main(int argc, char **argv) goto done; } ext++; - if (!strcmp(ext, "rwps") || !strcmp(ext, "rsbs") || !strcmp(ext, "rfms")) - { -#ifdef HAVE_REMOTE_LCD - screen = SCREEN_REMOTE; -#else - /* skip rwps etc. if not supported on this target (not an error) */ - continue; -#endif - } - else if (!strcmp(ext, "wps") || !strcmp(ext, "sbs") || !strcmp(ext, "fms")) - { - screen = SCREEN_MAIN; - } - else + + int valid = check_filetype(ext, &skin, &screen); + if (valid < 0) { printf("Invalid extension\n"); ret = 2; goto done; } - wps_screen = &screens[screen]; + else if (valid == 2) + { + if (!settings_load_config(name, false)) + { + printf("Bad configuration\n"); + ret = 3; + goto done; + } + printf("%s parsed OK\n\n", ext); + continue; + } + else if (valid > 0) + continue; /* skip (unsupported by this target but not an error) */ - res = skin_data_load(screen, &wps, name, true, &stats); + res = skin_data_load(skin, screen, &wps, name, true, &stats); if (!res) { - printf("WPS parsing failure\n"); + printf("%s parsing failure\n", ext); skin_error_format_message(); - ret = 3; + ret = 4; goto done; } - printf("WPS parsed OK\n\n"); + printf("%s parsed OK\n\n", ext); if (wps_verbose_level>2) skin_debug_tree(SKINOFFSETTOPTR(skin_buffer, wps.tree)); } diff --git a/tools/checkwps/checkwps.make b/tools/checkwps/checkwps.make index c103cdcc47..a337154f86 100644 --- a/tools/checkwps/checkwps.make +++ b/tools/checkwps/checkwps.make @@ -42,11 +42,22 @@ $(BUILDDIR)/$(BINARY): $$(CHECKWPS_OBJ) $$(CORE_LIBS) $(BUILDDIR)/fontbundle.h: $(ROOTDIR)/fonts/*bdf @echo FONTBUNDLE - $(SILENT)echo "static unsigned char* bundledfonts[] = {" > $@ + $(SILENT)echo "static const unsigned char* bundledfonts[] = {" > $@ $(SILENT)ls $(ROOTDIR)/fonts/*bdf | perl -pne 's|.*/(\d+-.*)\.bdf| "$$1",|;' >> $@ $(SILENT)echo " NULL, " >> $@ $(SILENT)echo "};" >> $@ +$(BUILDDIR)/themeassets.h: $(ROOTDIR)/wps/WPSLIST + @echo THEMEASSETS + $(SILENT)rm -Rf $(BUILDDIR)/.rockbox + $(SILENT)mkdir $(BUILDDIR)/.rockbox + $(SILENT)echo "static const unsigned char* themeassets[] = {" > $@ + $(SILENT)$(ROOTDIR)/wps/wpsbuild.pl $(ROOTDIR)/wps/WPSLIST $(TARGET) > /dev/null 2>&1 + $(SILENT)find $(BUILDDIR)/.rockbox -type f | perl -pne 's|.*/(\S+)\.\S+| "$$1",|;' >> $@ + $(SILENT)echo " NULL, " >> $@ + $(SILENT)echo "};" >> $@ + $(SILENT)rm -Rf $(BUILDDIR)/.rockbox + #### Everything below is hacked in from apps.make and lang.make $(BUILDDIR)/apps/features: $(ROOTDIR)/apps/features.txt diff --git a/tools/configure b/tools/configure index 6d798820fa..75215dd46e 100755 --- a/tools/configure +++ b/tools/configure @@ -1710,34 +1710,35 @@ cat <) { - if($_ =~ / +0x([0-9a-f]+) *_end = \./) { + if($_ =~ / +0x([0-9a-f]+) *_(bss)?end = \./) { $end = $1; last; } - elsif($_ =~ / +0x([0-9a-f]+) *_loadaddress = \./) { + elsif($_ =~ / +0x([0-9a-f]+) *_?loadaddress = \./) { $start = $1; } elsif($_ =~ / +0x([0-9a-f]+) *_dramcopystart = \./) { diff --git a/tools/r1_patcher/r1_patcher.sh b/tools/r1_patcher/r1_patcher.sh index 7cc102044b..e70e9b20a9 100755 --- a/tools/r1_patcher/r1_patcher.sh +++ b/tools/r1_patcher/r1_patcher.sh @@ -1,12 +1,10 @@ #!/bin/bash -#Prerequisites: -#sudo apt update && sudo apt install -y p7zip-full squashfs-tools genisoimage coreutils - -if [[ $# -ne 2 ]]; then - echo 'usage: ./r1_patcher.sh r1.upt bootloader.r1' >&2 - exit 1 -fi +# Linux Prerequisites: +# sudo apt update && sudo apt install -y p7zip-full squashfs-tools genisoimage coreutils +# +# macOS Prerequisites: +# brew install p7zip squashfs cdrtools coreutils # Fail fast on any error, undefined variable, or failed pipeline set -euo pipefail @@ -14,55 +12,159 @@ set -euo pipefail # Report errors with line number and the failing command trap 'echo "ERROR at line ${LINENO}: ${BASH_COMMAND}" >&2' ERR +# ============================================================================== +# macOS Compatibility Shim +# Map missing/incompatible GNU tools to their Homebrew equivalents via functions +# ============================================================================== +if [[ "$OSTYPE" == "darwin"* ]]; then + realpath() { grealpath "$@"; } + stat() { gstat "$@"; } + split() { gsplit "$@"; } + md5sum() { gmd5sum "$@"; } + genisoimage() { mkisofs "$@"; } + + # Quick sanity check for macOS users + for cmd in grealpath gstat gsplit gmd5sum mkisofs 7z unsquashfs mksquashfs; do + if ! command -v "$cmd" &> /dev/null; then + echo "ERROR: Missing macOS dependency for '$cmd'." >&2 + echo "Please run: brew install p7zip squashfs cdrtools coreutils" >&2 + exit 1 + fi + done +fi +# ============================================================================== + +usage() { + echo 'Usage:' >&2 + echo ' ./r1_patcher.sh r1.upt bootloader.r1' >&2 + echo '' >&2 + echo 'Advanced usage:' >&2 + echo ' ./r1_patcher.sh --unpack r1.upt [working_dir]' >&2 + echo ' ./r1_patcher.sh --inject-app bootloader.r1 [working_dir]' >&2 + echo ' ./r1_patcher.sh --pack [working_dir] [output.upt]' >&2 + exit 1 +} + +mode="" +if [[ $# -ge 1 && "$1" == --* ]]; then + mode="$1" + shift +fi + +case "$mode" in + "") + if [[ $# -ne 2 ]]; then + usage + fi + updatefile="$1" + bootloader="$2" + workingdir="$(realpath -m ./working_dir)" + updatefile_rb="${updatefile%.*}_rb.upt" + do_unpack=1 + do_inject=1 + do_pack=1 + do_cleanup=1 + ;; + --unpack) + if [[ $# -lt 1 || $# -gt 2 ]]; then + usage + fi + updatefile="$1" + workingdir="${2:-./working_dir}" + workingdir="$(realpath -m "$workingdir")" + do_unpack=1 + do_inject=0 + do_pack=0 + do_cleanup=0 + ;; + --inject-app) + if [[ $# -lt 1 || $# -gt 2 ]]; then + usage + fi + bootloader="$1" + workingdir="${2:-./working_dir}" + workingdir="$(realpath -m "$workingdir")" + do_unpack=0 + do_inject=1 + do_pack=0 + do_cleanup=0 + ;; + --pack) + if [[ $# -lt 1 || $# -gt 2 ]]; then + usage + fi + workingdir="${1:-./working_dir}" + workingdir="$(realpath -m "$workingdir")" + if [[ $# -ge 2 ]]; then + updatefile_rb="$2" + else + updatefile_rb="$(basename "$workingdir")_rb.upt" + fi + do_unpack=0 + do_inject=0 + do_pack=1 + do_cleanup=0 + ;; + *) + usage + ;; +esac + +currentdir="$(pwd)" + +if [[ -n "${updatefile_rb:-}" && "$updatefile_rb" != /* ]]; then + updatefile_rb="$currentdir/$updatefile_rb" +fi + ################################################################################ ### init ################################################################################ -currentdir="$(pwd)" -updatefile="$(basename "$1")" -updatefile_rb="${updatefile%.*}_rb.upt" - -workingdir="$(realpath ./working_dir)" workingdir_in="$workingdir/in" workingdir_out="$workingdir/out" -rm -rf "$workingdir" +if [[ "$do_unpack" -eq 1 ]]; then + rm -rf "$workingdir" -mkdir "$workingdir" -mkdir "$workingdir_in" -mkdir "$workingdir_out" + mkdir "$workingdir" + mkdir "$workingdir_in" + mkdir "$workingdir_out" +fi ################################################################################ ### extract ################################################################################ -# extract iso -mkdir -p "$workingdir_in/image_contents" -7z -o"$workingdir_in/image_contents" x "$updatefile" +if [[ "$do_unpack" -eq 1 ]]; then + # extract iso + mkdir -p "$workingdir_in/image_contents" + 7z -o"$workingdir_in/image_contents" x "$updatefile" -# create xImage (unchanged) -mkdir -p "$workingdir_out/xImage" -cat "$workingdir_in/image_contents/ota_v0"/xImage.* > "$workingdir_out/xImage/xImage" + # create xImage (unchanged) + mkdir -p "$workingdir_out/xImage" + cat "$workingdir_in/image_contents/ota_v0"/xImage.* > "$workingdir_out/xImage/xImage" -# create rootfs -mkdir -p "$workingdir_in/rootfs" -mkdir -p "$workingdir_in/rootfs/extracted" -cat "$workingdir_in/image_contents/ota_v0"/rootfs.squashfs.* > "$workingdir_in/rootfs/rootfs.squashfs" + # create rootfs + mkdir -p "$workingdir_in/rootfs" + mkdir -p "$workingdir_in/rootfs/extracted" + cat "$workingdir_in/image_contents/ota_v0"/rootfs.squashfs.* > "$workingdir_in/rootfs/rootfs.squashfs" -# extract rootfs -unsquashfs -f -d "$workingdir_in/rootfs/extracted" "$workingdir_in/rootfs/rootfs.squashfs" + # extract rootfs + unsquashfs -f -d "$workingdir_in/rootfs/extracted" "$workingdir_in/rootfs/rootfs.squashfs" +fi ################################################################################ ### update ################################################################################ -# copy 'bootloader' -bootloader_file="bootloader.rb" -cp "$2" "$workingdir_in/rootfs/extracted/usr/bin/$bootloader_file" -chmod 0755 "$workingdir_in/rootfs/extracted/usr/bin/$bootloader_file" +if [[ "$do_inject" -eq 1 ]]; then + # copy 'bootloader' + bootloader_file="bootloader.rb" + cp "$bootloader" "$workingdir_in/rootfs/extracted/usr/bin/$bootloader_file" + chmod 0755 "$workingdir_in/rootfs/extracted/usr/bin/$bootloader_file" -# create modified 'hiby_player.sh' script -cat << EOF > "$workingdir_in/rootfs/extracted/usr/bin/hiby_player.sh" + # create modified 'hiby_player.sh' script + cat << EOF > "$workingdir_in/rootfs/extracted/usr/bin/hiby_player.sh" #!/bin/sh killall hiby_player &>/dev/null @@ -74,14 +176,16 @@ killall -9 $bootloader_file &>/dev/null /usr/bin/$bootloader_file sleep 1s EOF -chmod 0755 "$workingdir_in/rootfs/extracted/usr/bin/hiby_player.sh" + chmod 0755 "$workingdir_in/rootfs/extracted/usr/bin/hiby_player.sh" +fi ################################################################################ ### Rockbox Hotplug Logic ################################################################################ -# 1. Create hotplug helper script -cat << 'EOF' > "$workingdir_in/rootfs/extracted/usr/bin/rb_hotplug.sh" +if [[ "$do_inject" -eq 1 ]]; then + # 1. Create hotplug helper script + cat << 'EOF' > "$workingdir_in/rootfs/extracted/usr/bin/rb_hotplug.sh" #!/bin/sh MNT_SD="/data/mnt/sd_0" MNT_USB="/data/mnt/usb" @@ -95,71 +199,74 @@ esac case "$ACTION" in add|"") mkdir -p "$MNT" - mount -t auto -o flush,noatime "/dev/$MDEV" "$MNT" + mount -t auto -o sync,noatime "/dev/$MDEV" "$MNT" ;; remove) umount -l "$MNT" ;; esac EOF -chmod 0755 "$workingdir_in/rootfs/extracted/usr/bin/rb_hotplug.sh" + chmod 0755 "$workingdir_in/rootfs/extracted/usr/bin/rb_hotplug.sh" -# 2. Check mdev.conf and append rules only if missing -MDEV_CONF="$workingdir_in/rootfs/extracted/etc/mdev.conf" + # 2. Check mdev.conf and append rules only if missing + MDEV_CONF="$workingdir_in/rootfs/extracted/etc/mdev.conf" -if ! grep -q "mmcblk" "$MDEV_CONF"; then - echo "mmcblk[0-9]p[0-9] 0:0 660 */usr/bin/rb_hotplug.sh" >> "$MDEV_CONF" -fi + if ! grep -q "mmcblk" "$MDEV_CONF"; then + echo "mmcblk[0-9]p[0-9] 0:0 660 */usr/bin/rb_hotplug.sh" >> "$MDEV_CONF" + fi -if ! grep -q "sd\[a-z\]" "$MDEV_CONF"; then - echo "sd[a-z][0-9] 0:0 660 */usr/bin/rb_hotplug.sh" >> "$MDEV_CONF" + if ! grep -q "sd\[a-z\]" "$MDEV_CONF"; then + echo "sd[a-z][0-9] 0:0 660 */usr/bin/rb_hotplug.sh" >> "$MDEV_CONF" + fi fi ################################################################################ ### rebuild ################################################################################ -mkdir -p "$workingdir_out/image_contents/ota_v0" +if [[ "$do_pack" -eq 1 ]]; then + mkdir -p "$workingdir_out/image_contents/ota_v0" -mksquashfs "$workingdir_in/rootfs/extracted" "$workingdir_out/rootfs.squashfs" -comp lzo -all-root + rm -f "$workingdir_out/rootfs.squashfs" + mksquashfs "$workingdir_in/rootfs/extracted" "$workingdir_out/rootfs.squashfs" -comp lzo -all-root -noappend -cd "$workingdir_out/image_contents/ota_v0" + cd "$workingdir_out/image_contents/ota_v0" -# rootfs.squashfs -split -b 512k "$workingdir_out/rootfs.squashfs" --numeric-suffixes=0 -a 4 rootfs.squashfs. + # rootfs.squashfs + split -b 512k "$workingdir_out/rootfs.squashfs" --numeric-suffixes=0 -a 4 rootfs.squashfs. -rootfs_md5=($(md5sum "$workingdir_out/rootfs.squashfs")) -rootfs_size=$(stat -c%s "$workingdir_out/rootfs.squashfs") -md5=$rootfs_md5 + rootfs_md5=($(md5sum "$workingdir_out/rootfs.squashfs")) + rootfs_size=$(stat -c%s "$workingdir_out/rootfs.squashfs") + md5=$rootfs_md5 -ota_md5_rootfs="ota_md5_rootfs.squashfs.$md5" + ota_md5_rootfs="ota_md5_rootfs.squashfs.$md5" -for part in $(ls rootfs.squashfs.[0-9]* | sort); do - md5next=($(md5sum "$part")) - echo $md5next >> $ota_md5_rootfs - mv "$part" "$part.$md5" - md5=$md5next -done + for part in $(ls rootfs.squashfs.[0-9]* | sort); do + md5next=($(md5sum "$part")) + echo $md5next >> $ota_md5_rootfs + mv "$part" "$part.$md5" + md5=$md5next + done -# xImage -split -b 512k "$workingdir_out/xImage/xImage" --numeric-suffixes=0 -a 4 xImage. + # xImage + split -b 512k "$workingdir_out/xImage/xImage" --numeric-suffixes=0 -a 4 xImage. -ximage_md5=($(md5sum "$workingdir_out/xImage/xImage")) -ximage_size=$(stat -c%s "$workingdir_out/xImage/xImage") -md5=$ximage_md5 + ximage_md5=($(md5sum "$workingdir_out/xImage/xImage")) + ximage_size=$(stat -c%s "$workingdir_out/xImage/xImage") + md5=$ximage_md5 -ota_md5_xImage="ota_md5_xImage.$md5" + ota_md5_xImage="ota_md5_xImage.$md5" -for part in $(ls xImage.[0-9]* | sort); do - md5next=($(md5sum "$part")) - echo $md5next >> $ota_md5_xImage - mv "$part" "$part.$md5" - md5=$md5next -done + for part in $(ls xImage.[0-9]* | sort); do + md5next=($(md5sum "$part")) + echo $md5next >> $ota_md5_xImage + mv "$part" "$part.$md5" + md5=$md5next + done -# ota_update.in + # ota_update.in -echo "ota_version=0 + echo "ota_version=0 img_type=kernel img_name=xImage @@ -172,20 +279,30 @@ img_size=$rootfs_size img_md5=$rootfs_md5 " > ota_update.in -echo > ota_v0.ok + echo > ota_v0.ok -# ota_config.in -cd "$workingdir_out/image_contents" + # ota_config.in + cd "$workingdir_out/image_contents" -echo "current_version=0" > ota_config.in + echo "current_version=0" > ota_config.in -# iso -genisoimage -f -U -J -joliet-long -r -allow-lowercase -allow-multidot -o "$currentdir/$updatefile_rb" "$workingdir_out/image_contents/" + # iso + genisoimage -f -U -J -joliet-long -r -allow-lowercase -allow-multidot -input-charset utf-8 -o "$updatefile_rb" "$workingdir_out/image_contents/" +fi ################################################################################ ### cleanup ################################################################################ -rm -rf "$workingdir" +if [[ "$do_cleanup" -eq 1 ]]; then + rm -rf "$workingdir" +else + if [[ "$do_unpack" -eq 1 ]]; then + echo "Extracted to: $workingdir/in/rootfs/extracted" + fi +fi +if [[ "$do_pack" -eq 1 ]]; then + echo "Image file created: $updatefile_rb" +fi exit 0 diff --git a/tools/rkw.c b/tools/rkw.c index db86b4f748..6d5f4c61d3 100644 --- a/tools/rkw.c +++ b/tools/rkw.c @@ -26,6 +26,14 @@ #define RKLD_MAGIC 0x4c44524b #define RKW_HEADER_SIZE 0x2c +/* The NAND bootloader writes three handoff words at the address in + * header field 0x14 before it jumps to the image. Point it at the last + * 12 bytes of DRAM, which nothing touches before our bootloader reads + * them. Every rk27xx target has 16 MB; keep in step with + * RKW_HANDOFF_ADDR in firmware/target/arm/rk27xx/rkw-loader.c. + */ +#define RKW_HANDOFF_ADDR (0x60000000 + 16 * 0x100000 - 12) + /* slightly modified version from crc32.c in rockbox */ static uint32_t rkw_crc32(const void *src, uint32_t len) { @@ -114,7 +122,7 @@ int rkw_encode(char *iname, char *oname, unsigned long modelnum) int2le(0x60000000, outbuf+0x08); /* base address */ int2le(0x60000000, outbuf+0x0c); /* load address */ int2le(0x60000000+length, outbuf+0x10); /* end address */ - int2le(0x6035a5e4, outbuf+0x14); /* points to some unknown struct */ + int2le(RKW_HANDOFF_ADDR, outbuf+0x14); /* loader handoff words */ int2le(modelnum, outbuf+0x18); /* reserved (we abuse the format * to store modelnum here */ diff --git a/tools/rockboxdev.sh b/tools/rockboxdev.sh index 8b8d4d204a..d5b7bf2917 100755 --- a/tools/rockboxdev.sh +++ b/tools/rockboxdev.sh @@ -852,7 +852,7 @@ do # glibcopts="--enable-kernel=2.6.23 --enable-oldest-abi=2.4" build_linux_toolchain "arm-rockbox-linux-gnueabi" "2.38" "" "" "9.5.0" \ - "$gccopts" "2.6.32.71" "" "2.20" "$glibcopts" "glibc-220-make44.patch glibc-2.20-gcc10.patch" + "$gccopts" "2.6.32.71" "" "2.20" "$glibcopts" "glibc-220-make44.patch glibc-2.20-gcc10.patch glibc-220-texinfo.patch" # build alsa-lib # we need to set the prefix to how it is on device (/usr) and then # tweak install dir at make install step @@ -882,7 +882,7 @@ do glibcopts="--enable-kernel=3.2 --enable-oldest-abi=2.16 --disable-werror" # FIXME: maybe add -mhard-float? build_linux_toolchain "mipsel-rockbox-linux-gnu" "2.38" "" "binutils-c23.patch" "9.5.0" \ - "$gccopts" "3.10.108" "linux-c23.patch" "2.27" "$glibcopts" "glibc-227-make44.patch" + "$gccopts" "3.10.108" "linux-c23.patch" "2.27" "$glibcopts" "glibc-227-make44.patch glibc-227-texinfo.patch" # build alsa-lib # we need to set the prefix to how it is on device (/usr) and then # tweak install dir at make install step diff --git a/tools/root.make b/tools/root.make index 11540a0a39..eb574fcabf 100644 --- a/tools/root.make +++ b/tools/root.make @@ -176,8 +176,8 @@ else # core include $(ROOTDIR)/packaging/rgnano/rgnano.make endif - ifneq (,$(findstring ctru, $(APP_TYPE))) - include $(ROOTDIR)/packaging/ctru/ctru.make + ifneq (,$(findstring 3ds-homebrew, $(APP_TYPE))) + include $(ROOTDIR)/packaging/3ds/3ds.make endif endif # bootloader @@ -236,7 +236,7 @@ clean:: $(LINKRAM) $(LINKROM) rockbox.elf rockbox.map rockbox.bin \ make.dep rombox.elf rombox.map rombox.bin romstart.txt \ $(BINARY) $(FLASHFILE) uisimulator bootloader flash $(BOOTLINK) \ - rockbox.apk lang_enum.h rbversion.h fontbundle.h 3ds rockbox.3dsx \ + rockbox.apk lang_enum.h rbversion.h fontbundle.h themeassets.h 3ds rockbox.3dsx \ rockbox.bnr rockbox.cia rockbox.icn rockbox.smdh #### linking the binaries: #### @@ -351,6 +351,9 @@ $(BUILDDIR)/rockbox.zip: build endif $(call PRINTS,ZIP rockbox$(ZIPFILESUFFIX).zip) $(SILENT)$(TOOLSDIR)/buildzip.pl $(VERBOSEOPT) --app=$(APPLICATION) -m \"$(MODELNAME)\" -i \"$(TARGET_ID)\" -o $(BUILDDIR)/rockbox$(ZIPFILESUFFIX).zip -r "$(ROOTDIR)" --rbdir="$(RBDIR)" $(BUILDZIPOPTS) $(TARGET) $(BINARY) +ifneq ($(REPRODUCIBLE_ZIP),) + $(SILENT)strip-nondeterminism $(BUILDDIR)/rockbox$(ZIPFILESUFFIX).zip +endif mapzip: $(SILENT)find . -name "*.map" | xargs zip rockbox-maps.zip @@ -423,8 +426,8 @@ talkclips-force: talkclips endif -ifeq (,$(findstring android, $(APP_TYPE))) - +# only simulators get a 'make install' +ifneq (,$(findstring sdl,$(APP_TYPE))) simext1: $(SILENT)mkdir -p $@ diff --git a/tools/sapi_voice.vbs b/tools/sapi_voice.vbs index f39be986d0..0242b812f8 100644 --- a/tools/sapi_voice.vbs +++ b/tools/sapi_voice.vbs @@ -37,16 +37,16 @@ Const STDERR = 2 Dim oShell, oArgs, oEnv Dim oFSO, oStdIn, oStdOut -Dim bVerbose, bSAPI4, bList +Dim bVerbose, bList Dim bMSSP -Dim sLanguage, sVoice, sSpeed, sName, sVendor +Dim sLanguage, sVoice, sSpeed, sVolume, sName, sVendor Dim oSpVoice, oSpFS ' SAPI5 voice and filestream -Dim oTTS, nMode ' SAPI4 TTS object, mode selector Dim oVoice ' for traversing the list of voices Dim nLangID, sSelectString Dim aLine, aData ' used in command reading +Dim nError, sError ' error returned to the controlling process On Error Resume Next @@ -59,142 +59,81 @@ Set oEnv = oShell.Environment("Process") bVerbose = (oEnv("V") <> "") Set oArgs = WScript.Arguments.Named -bSAPI4 = oArgs.Exists("sapi4") bMSSP = oArgs.Exists("mssp") bList = oArgs.Exists("listvoices") sLanguage = oArgs.Item("language") sVoice = oArgs.Item("voice") sSpeed = oArgs.Item("speed") +sVolume = oArgs.Item("volume") +' Create SAPI5 object +If bMSSP Then + Set oSpVoice = CreateObject("speech.SpVoice") +Else + Set oSpVoice = CreateObject("SAPI.SpVoice") +End If +If Err.Number <> 0 Then + WScript.StdErr.WriteLine "Error " & Err.Number _ + & " - could not get SpVoice object." _ + & " SAPI 5 not installed?" + WScript.Quit 1 +End If -If bSAPI4 Then - ' Create SAPI4 ActiveVoice object - Set oTTS = WScript.CreateObject("ActiveVoice.ActiveVoice", "TTS_") - If Err.Number <> 0 Then - Err.Clear - Set oTTS = WScript.CreateObject("ActiveVoice.ActiveVoice.1", "TTS_") - If Err.Number <> 0 Then - WScript.StdErr.WriteLine "Error - could not get ActiveVoice" _ - & " object. SAPI 4 not installed?" - WScript.Quit 1 - End If - End If - oTTS.Initialized = 1 - - If bList Then - ' Just list available voices for the selected language - For Each nLangID in LangIDs(sLanguage) - For nMode = 1 To oTTS.CountEngines - If oTTS.LanguageID(nMode) = nLangID Then - WScript.StdErr.Write oTTS.ModeName(nMode) & "," - End If - Next - Next - WScript.StdErr.WriteLine - WScript.Quit 0 - End If - - ' Select matching voice - For Each nLangID in LangIDs(sLanguage) - sSelectString = "LanguageID=" & nLangID - If sVoice <> "" Then - sSelectString = sSelectString & ";Speaker=" & sVoice _ - & ";ModeName=" & sVoice - End If - nMode = oTTS.Find(sSelectString) - If oTTS.LanguageID(nMode) = nLangID And (sVoice = "" Or _ - oTTS.Speaker(nMode) = sVoice Or oTTS.ModeName(nMode) = sVoice) Then - sName = oTTS.ModeName(nMode) - If bVerbose Then - WScript.StdErr.WriteLine "Using " & sName & " for " & sSelectString - End If - Exit For - Else - sSelectString = "" - End If - Next - If sSelectString = "" Then - WScript.StdErr.WriteLine "Error - found no matching voice for " _ - & sLanguage & ", " & sVoice - WScript.Quit 1 - End If - oTTS.Select nMode - - ' Speed selection - If sSpeed <> "" Then oTTS.Speed = sSpeed - - ' Get vendor information - sVendor = oTTS.MfgName(nMode) - -Else ' SAPI5 - ' Create SAPI5 object - If bMSSP Then - Set oSpVoice = CreateObject("speech.SpVoice") - Else - Set oSpVoice = CreateObject("SAPI.SpVoice") - End If - If Err.Number <> 0 Then - WScript.StdErr.WriteLine "Error " & Err.Number _ - & " - could not get SpVoice object." _ - & " SAPI 5 not installed?" - WScript.Quit 1 - End If - - If bList Then - ' Just list available voices for the selected language - For Each nLangID in LangIDs(sLanguage) - sSelectString = "Language=" & Hex(nLangID) - For Each oVoice in oSpVoice.GetVoices(sSelectString) - WScript.StdErr.Write oVoice.GetAttribute("Name") & ";" - Next - Next - WScript.StdErr.WriteLine - WScript.Quit 0 - End If - - ' Select matching voice +If bList Then + ' Just list available voices for the selected language For Each nLangID in LangIDs(sLanguage) sSelectString = "Language=" & Hex(nLangID) - If sVoice <> "" Then - sSelectString = sSelectString & ";Name=" & sVoice - End If - Set oSpVoice.Voice = oSpVoice.GetVoices(sSelectString).Item(0) - If Err.Number = 0 Then - sName = oSpVoice.Voice.GetAttribute("Name") - If bVerbose Then - WScript.StdErr.WriteLine "Using " & sName & " for " & sSelectString - End If - Exit For - Else - sSelectString = "" - Err.Clear - End If + For Each oVoice in oSpVoice.GetVoices(sSelectString) + WScript.StdErr.Write oVoice.GetAttribute("Name") & ";" + Next Next - If sSelectString = "" Then - WScript.StdErr.WriteLine "Error - found no matching voice for " _ - & sLanguage & ", " & sVoice - WScript.Quit 1 - End If - - ' Speed selection - If sSpeed <> "" Then oSpVoice.Rate = sSpeed - - ' Get vendor information, protect from missing attribute - sVendor = oSpVoice.Voice.GetAttribute("Vendor") - If Err.Number <> 0 Then - Err.Clear - sVendor = "(unknown)" - ' Some L&H engines don't set the vendor attribute - check the name - If Len(sName) > 3 And Left(sName, 3) = "LH " Then - sVendor = "L&H" - End If - End If - - ' Filestream object for output - Set oSpFS = CreateObject("SAPI.SpFileStream") - oSpFS.Format.Type = AudioFormat(sVendor) + WScript.StdErr.WriteLine + WScript.Quit 0 End If +' Select matching voice +For Each nLangID in LangIDs(sLanguage) + sSelectString = "Language=" & Hex(nLangID) + If sVoice <> "" Then + sSelectString = sSelectString & ";Name=" & sVoice + End If + Set oSpVoice.Voice = oSpVoice.GetVoices(sSelectString).Item(0) + If Err.Number = 0 Then + sName = oSpVoice.Voice.GetAttribute("Name") + If bVerbose Then + WScript.StdErr.WriteLine "Using " & sName & " for " & sSelectString + End If + Exit For + Else + sSelectString = "" + Err.Clear + End If +Next +If sSelectString = "" Then + WScript.StdErr.WriteLine "Error - found no matching voice for " _ + & sLanguage & ", " & sVoice + WScript.Quit 1 +End If + +' Speed selection +If sSpeed <> "" Then oSpVoice.Rate = sSpeed +' Volume selection +If sVolume <> "" Then oSpVoice.Volume = sVolume +' Get vendor information, protect from missing attribute +sVendor = oSpVoice.Voice.GetAttribute("Vendor") +If Err.Number <> 0 Then + Err.Clear + sVendor = "(unknown)" + ' Some L&H engines don't set the vendor attribute - check the name + If Len(sName) > 3 And Left(sName, 3) = "LH " Then + sVendor = "L&H" + End If +End If + +' Filestream object for output +Set oSpFS = CreateObject("SAPI.SpFileStream") +oSpFS.Format.Type = AudioFormat(sVendor) + Do aLine = Split(oStdIn.ReadLine, vbTab, 2) If Err.Number <> 0 Then @@ -211,18 +150,26 @@ Do aData = Split(aLine(1), vbTab, 2) If bVerbose Then WScript.StdErr.WriteLine "Saying " & aData(1) _ & " in " & aData(0) - If bSAPI4 Then - oTTS.FileName = aData(0) - oTTS.Speak aData(1) - While oTTS.Speaking - WScript.Sleep 1 - Wend - oTTS.FileName = "" - Else - oSpFS.Open aData(0), SSFMCreateForWrite, false + Err.Clear + oSpFS.Open aData(0), SSFMCreateForWrite, false + nError = Err.Number + sError = Err.Description + If nError = 0 Then Set oSpVoice.AudioOutputStream = oSpFS oSpVoice.Speak aData(1) + nError = Err.Number + sError = Err.Description oSpFS.Close + If nError = 0 And Err.Number <> 0 Then + nError = Err.Number + sError = Err.Description + End If + End If + If nError <> 0 Then + oStdOut.WriteLine "ERROR" & vbTab & nError & ": " & sError + Err.Clear + ElseIf Not oFSO.FileExists(aData(0)) Then + oStdOut.WriteLine "ERROR" & vbTab & "SAPI reported success but created no wave file" End If Case "EXEC" If bVerbose Then WScript.StdErr.WriteLine "> " & aLine(1) @@ -359,6 +306,8 @@ Function LangIDs(ByRef sLanguage) LangIDs = Array(&h427) Case "magyar" LangIDs = Array(&h40e) + Case "moldoveneste" + LangIDs = Array(&h818) Case "nederlands" LangIDs = Array(&h413, &h813) ' Standard, Belgian Case "norsk-nynorsk" diff --git a/tools/scramble.c b/tools/scramble.c index 85879e40f3..0d4bc69bd9 100644 --- a/tools/scramble.c +++ b/tools/scramble.c @@ -80,9 +80,9 @@ void usage(void) "\t-iaudiox5v iAudio X5V format\n" "\t-iaudiom5 iAudio M5 format\n" "\t-iaudiom3 iAudio M3 format\n"); - printf("\t-ipod3g ipod firmware partition format (3rd Gen)\n" - "\t-ipod4g ipod firmware partition format (4th Gen, Mini, Nano, Photo/Color)\n" - "\t-ipod5g ipod firmware partition format (5th Gen - aka Video)\n" + printf("\t-ipod3g iPod firmware partition format (3rd Gen)\n" + "\t-ipod4g iPod firmware partition format (4th Gen, Mini, Nano, Photo/Color)\n" + "\t-ipod5g iPod firmware partition format (5th Gen - aka Video)\n" "\t-creative=X [-no-ciff] Creative firmware structure format\n" "\t (X values: zenv, zen, zenxfi, zenmozaic)\n"); printf("\t-gigabeat Toshiba Gigabeat F/X format\n" diff --git a/tools/thumb-cc.py b/tools/thumb-cc.py index 9e2b96c346..a7b31aead5 100755 --- a/tools/thumb-cc.py +++ b/tools/thumb-cc.py @@ -31,7 +31,11 @@ if '-o' in args and args.index('-o') < len(args) - 1: if len(args[args.index('-o') + 1].rsplit('.o', 1)) == 1: execv(args[0], args) # output doesn't end in .o -args.append('-mthumb-interwork') # thumb-interwork is required +# thumb-interwork is required; add if not present +if '-mthumb-interwork' not in args: + args += ['-mthumb-interwork']; + +# Try to compile in thumb mode first gcc = Popen(args + ['-mthumb'], stdout=PIPE, stderr=PIPE) (out, err) = gcc.communicate() diff --git a/tools/toolchain-patches/glibc-220-texinfo.patch b/tools/toolchain-patches/glibc-220-texinfo.patch new file mode 100644 index 0000000000..cd2ab9e565 --- /dev/null +++ b/tools/toolchain-patches/glibc-220-texinfo.patch @@ -0,0 +1,12 @@ +diff -aur glibc-2.20/configure glibc-2.20-patch/configure +--- glibc-2.20/configure 2014-09-07 04:09:09.000000000 -0400 ++++ glibc-2.20-patch/configure 2026-09-28 12:33:47.509475820 -0400 +@@ -4851,7 +4851,7 @@ + ac_prog_version=`$MAKEINFO --version 2>&1 | sed -n 's/^.*GNU texinfo.* \([0-9][0-9.]*\).*$/\1/p'` + case $ac_prog_version in + '') ac_prog_version="v. ?.??, bad"; ac_verc_fail=yes;; +- 4.[5-9]*|4.[1-9][0-9]*|[5-9].*) ++ 4.[5-9]*|4.[1-9][0-9]*|[5-6].*|7.[2-9]*|[8-9].*) + ac_prog_version="$ac_prog_version, ok"; ac_verc_fail=no;; + *) ac_prog_version="$ac_prog_version, bad"; ac_verc_fail=yes;; + diff --git a/tools/toolchain-patches/glibc-227-texinfo.patch b/tools/toolchain-patches/glibc-227-texinfo.patch new file mode 100644 index 0000000000..597c9aeb2f --- /dev/null +++ b/tools/toolchain-patches/glibc-227-texinfo.patch @@ -0,0 +1,12 @@ +diff -aur glibc-2.27/configure glibc-2.27-patch/configure +--- glibc-2.27/configure 2018-02-01 11:17:18.000000000 -0500 ++++ glibc-2.27-patch/configure 2026-09-28 12:36:55.593733872 -0400 +@@ -4814,7 +4814,7 @@ + ac_prog_version=`$MAKEINFO --version 2>&1 | sed -n 's/^.*GNU texinfo.* \([0-9][0-9.]*\).*$/\1/p'` + case $ac_prog_version in + '') ac_prog_version="v. ?.??, bad"; ac_verc_fail=yes;; +- 4.[7-9]*|4.[1-9][0-9]*|[5-9].*) ++ 4.[5-9]*|4.[1-9][0-9]*|[5-6].*|7.[2-9]*|[8-9].*) + ac_prog_version="$ac_prog_version, ok"; ac_verc_fail=no;; + *) ac_prog_version="$ac_prog_version, bad"; ac_verc_fail=yes;; + diff --git a/tools/version.sh b/tools/version.sh index 2fcdcd02c4..f2b2196ee5 100755 --- a/tools/version.sh +++ b/tools/version.sh @@ -50,7 +50,11 @@ if [ -z $VERSION ]; then # Ok, we need to derive it from the Version Control system VER=`gitversion $TOP` fi -VERSION=$VER-`date -u +%y%m%d` +if [ -z $SOURCE_DATE_EPOCH ]; then + VERSION=$VER-`date -u +%y%m%d` +else + VERSION=$VER-`date -d @$SOURCE_DATE_EPOCH -u +%y%m%d` +fi fi echo $VERSION diff --git a/tools/voice.pl b/tools/voice.pl index 6eb5d6f4e3..53c153f449 100755 --- a/tools/voice.pl +++ b/tools/voice.pl @@ -754,6 +754,37 @@ sub panic_cleanup { die "moo"; } +sub gensingleclip { + our $verbose; + my ($enc, $voice, $tts_object, $language, $encoder, $encoder_opts, $tts_engine_opts) = @_; + + my $format = $tts_object->{'format'}; + my $wav = sprintf("%s.talk.wav", $enc); + + #Apply corrections + $voice = correct_string($voice, $language, $tts_object); + + printf("Talkclip %s: %s\n", $enc, $voice) if $verbose; + # Don't generate encoded file if it already exists + return if (-f $enc && !$force); + + voicestring($voice, $wav, $tts_engine_opts, $tts_object); + wavtrim($wav, $trim_thresh, $tts_object); + + if ($format eq "mp3") { + system("ffmpeg -loglevel 0 -i $wav $voice$wav"); + rename("$voice$wav","$wav"); + $format = "wav"; + } + if ($format eq "wav") { + encodewav($wav, $enc, $encoder, $encoder_opts, $tts_object); + } else { + copy($wav, $enc); + } + synchronize($tts_object); + unlink($wav); +} + # Generate .talk clips sub gentalkclips { our $verbose; @@ -762,8 +793,7 @@ sub gentalkclips { while (my $file = $d->read) { $file = Encode::decode( locale_fs => $file); - my ($voice, $wav, $enc); - my $format = $tts_object->{'format'}; + my ($voice, $enc); # Ignore dot-dirs and talk files if ($file eq '.' || $file eq '..' || $file =~ /\.talk$/) { @@ -783,40 +813,38 @@ sub gentalkclips { # Convert to a complete path my $path = sprintf("%s/%s", $dir, $file); - $wav = sprintf("%s.talk.wav", $path); - if ( -d $path) { # Element is a dir $enc = sprintf("%s/_dirname.talk", $path); if (! -e "$path/talkclips.ignore") { # Skip directories containing "talkclips.ignore" gentalkclips($path, $tts_object, $language, $encoder, $encoder_opts, $tts_engine_opts, $i); } + gensingleclip($enc, $voice, $tts_object, $language, $encoder, $encoder_opts, $tts_engine_opts); + } elsif ($path =~ /\.cue$/i) { + # Cuesheet. Generate a clip for the cuesheet itself... + $enc = sprintf("%s.talk", $path); + $voice =~ s/\.[^\.]*$//; # Trim extension + gensingleclip($enc, $voice, $tts_object, $language, $encoder, $encoder_opts, $tts_engine_opts); + + # Parse the cuesheet + open(CUE, "<$path"); + + while() { + chomp; + if (/PERFORMER/ || /TITLE/) { + /^\s*(TITLE|PERFORMER)\s+["'](.*)["']/; + $voice = $2; + $enc = dirname($path) . "/$voice.talk"; + #print ("\V: ``$voice''\n -> $enc\n"); + gensingleclip($enc, $voice, $tts_object, $language, $encoder, $encoder_opts, $tts_engine_opts); + } + } + + close(CUE); } else { # Element is a file $enc = sprintf("%s.talk", $path); $voice =~ s/\.[^\.]*$//; # Trim extension + gensingleclip($enc, $voice, $tts_object, $language, $encoder, $encoder_opts, $tts_engine_opts); } - - # Apply corrections - $voice = correct_string($voice, $language, $tts_object); - - printf("Talkclip %s: %s\n", $enc, $voice) if $verbose; - # Don't generate encoded file if it already exists - next if (-f $enc && !$force); - - voicestring($voice, $wav, $tts_engine_opts, $tts_object); - wavtrim($wav, $trim_thresh, $tts_object); - - if ($format eq "mp3") { - system("ffmpeg -loglevel 0 -i $wav $voice$wav"); - rename("$voice$wav","$wav"); - $format = "wav"; - } - if ($format eq "wav") { - encodewav($wav, $enc, $encoder, $encoder_opts, $tts_object); - } else { - copy($wav, $enc); - } - synchronize($tts_object); - unlink($wav); } } diff --git a/uisimulator/bitmaps/UI-hidizsap80max.bmp b/uisimulator/bitmaps/UI-hidizsap80max.bmp new file mode 100644 index 0000000000..0ecd8362c6 Binary files /dev/null and b/uisimulator/bitmaps/UI-hidizsap80max.bmp differ diff --git a/uisimulator/bitmaps/UI-samsungypcp3.bmp b/uisimulator/bitmaps/UI-samsungypcp3.bmp new file mode 100644 index 0000000000..caea28a9f6 Binary files /dev/null and b/uisimulator/bitmaps/UI-samsungypcp3.bmp differ diff --git a/uisimulator/buttonmap/SOURCES b/uisimulator/buttonmap/SOURCES index 4efbdee874..8dc2ccc7d5 100644 --- a/uisimulator/buttonmap/SOURCES +++ b/uisimulator/buttonmap/SOURCES @@ -62,7 +62,11 @@ sansa-connect.c #elif CONFIG_KEYPAD == SONY_NWZ_PAD sony-nwz.c #elif CONFIG_KEYPAD == SAMSUNG_YPR0_PAD +#ifdef SAMSUNG_YPCP3 +samsung-ypcp3.c +#else samsung-ypr0.c +#endif #elif CONFIG_KEYPAD == CREATIVE_ZEN_PAD creative-zen.c #elif CONFIG_KEYPAD == SONY_NWZA860_PAD @@ -87,5 +91,7 @@ shanling-q1.c rg-nano.c #elif CONFIG_KEYPAD == HIBY_R3PROII_PAD hiby-r3proii.c +#elif CONFIG_KEYPAD == HIDIZS_AP80MAX_PAD +hidizs-ap80max.c #endif #endif /* SIMULATOR */ diff --git a/uisimulator/buttonmap/hidizs-ap80max.c b/uisimulator/buttonmap/hidizs-ap80max.c new file mode 100644 index 0000000000..295267bdb0 --- /dev/null +++ b/uisimulator/buttonmap/hidizs-ap80max.c @@ -0,0 +1,57 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2021 by Solomon Peachy + * Copyright (C) 2026 by Jerry Shen + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + + +#include +#include "button.h" +#include "buttonmap.h" + +int key_to_button(int keyboard_button) +{ + switch (keyboard_button) + { + case SDLK_UP: + return BUTTON_SCROLL_BACK; + case SDLK_DOWN: + return BUTTON_SCROLL_FWD; + case SDLK_LEFT: + return BUTTON_PREV; + case SDLK_RIGHT: + return BUTTON_NEXT; + case SDLK_RETURN: + case SDLK_SPACE: + return BUTTON_PLAY; + case SDLK_ESCAPE: + return BUTTON_POWER; + default: + return BUTTON_NONE; + } +} + +struct button_map bm[] = { + { SDLK_ESCAPE, 525, 270, 35, "Power" }, + { SDLK_LEFT, 526, 432, 25, "Previous" }, + { SDLK_SPACE, 526, 515, 25, "Play" }, + { SDLK_RIGHT, 526, 598, 25, "Next" }, + { SDLK_UP, 525, 230, 35, "Scroll Fwd" }, + { SDLK_DOWN, 525, 310, 35, "Scroll Back" }, + { 0, 0, 0, 0, "None" } +}; diff --git a/uisimulator/buttonmap/samsung-ypcp3.c b/uisimulator/buttonmap/samsung-ypcp3.c new file mode 100644 index 0000000000..b678a95722 --- /dev/null +++ b/uisimulator/buttonmap/samsung-ypcp3.c @@ -0,0 +1,85 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2026 by Marcin Bukat + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + + +#include +#include "button.h" +#include "buttonmap.h" + +/* The YP-CP3 has the YP-R0's keys, and the same keyboard layout here */ +int key_to_button(int keyboard_button) +{ + int new_btn = BUTTON_NONE; + switch (keyboard_button) + { + case SDLK_KP_4: + case SDLK_LEFT: + new_btn = BUTTON_LEFT; + break; + case SDLK_KP_6: + case SDLK_RIGHT: + new_btn = BUTTON_RIGHT; + break; + case SDLK_KP_8: + case SDLK_UP: + new_btn = BUTTON_UP; + break; + case SDLK_KP_2: + case SDLK_DOWN: + new_btn = BUTTON_DOWN; + break; + case SDLK_PAGEUP: + case SDLK_KP_9: + new_btn = BUTTON_MENU; + break; + case SDLK_KP_7: + new_btn = BUTTON_BACK; + break; + case SDLK_KP_5: + case SDLK_SPACE: + case SDLK_KP_ENTER: + case SDLK_RETURN: + new_btn = BUTTON_SELECT; + break; + case SDLK_KP_3: + new_btn = BUTTON_POWER; + break; + case SDLK_KP_1: + new_btn = BUTTON_USER; + break; + } + return new_btn; +} + +/* The face carries the joystick and, below it, Back and Menu; User (Rec) + * and Power sit on the top edge, above them. Select comes first: the first + * area a click falls in wins. */ +struct button_map bm[] = { + { SDLK_KP_5, 517, 90, 11, "Select" }, + { SDLK_KP_8, 517, 66, 13, "Up" }, + { SDLK_KP_2, 517, 114, 13, "Down" }, + { SDLK_KP_4, 493, 90, 13, "Left" }, + { SDLK_KP_6, 541, 90, 13, "Right" }, + { SDLK_KP_7, 519, 159, 11, "Back" }, + { SDLK_KP_9, 519, 193, 11, "Menu" }, + { SDLK_KP_1, 480, 8, 12, "User" }, + { SDLK_KP_3, 531, 8, 12, "Power" }, + { 0, 0, 0, 0, "None" } +}; diff --git a/uisimulator/common/filesystem-sim.c b/uisimulator/common/filesystem-sim.c index 16e852901b..aa7d394346 100644 --- a/uisimulator/common/filesystem-sim.c +++ b/uisimulator/common/filesystem-sim.c @@ -467,7 +467,7 @@ int sim_ftruncate(int fildes, off_t length) if (!filestr) return -1; - off_t size = os_filesize(filestr->osfd); + off_t size = os_ffilesize(filestr->osfd); if (size < 0) return -1; @@ -582,13 +582,13 @@ int sim_modtime(const char *path, time_t modtime) return os_modtime(ospath, modtime); } -off_t sim_filesize(int fildes) +off_t sim_ffilesize(int fildes) { struct filestr_desc *filestr = get_filestr(fildes); if (!filestr) return -1; - return os_filesize(filestr->osfd); + return os_ffilesize(filestr->osfd); } int sim_fsamefile(int fildes1, int fildes2) @@ -764,7 +764,7 @@ bool sim_dir_exists(const char *dirname) return true; } -struct dirinfo dir_get_info(DIR *dirp, struct sim_dirent *entry) +struct dirinfo sim_dir_get_info(DIR *dirp, struct sim_dirent *entry) { int rc; struct dirstr_desc *dirstr = get_dirstr(dirp); diff --git a/uisimulator/common/filesystem-sim.h b/uisimulator/common/filesystem-sim.h index 38fff3bd4e..8e50709f85 100644 --- a/uisimulator/common/filesystem-sim.h +++ b/uisimulator/common/filesystem-sim.h @@ -68,7 +68,7 @@ ssize_t sim_write(int fildes, const void *buf, size_t nbyte); int sim_remove(const char *path); int sim_rename(const char *old, const char *new); int sim_modtime(const char *path, time_t modtime); -off_t sim_filesize(int fildes); +off_t sim_ffilesize(int fildes); int sim_fsamefile(int fildes1, int fildes2); int sim_relate(const char *path1, const char *path2); bool sim_file_exists(const char *path); @@ -102,6 +102,9 @@ int sim_mkdir(const char *path); int sim_rmdir(const char *path); int sim_samedir(DIR *dirp1, DIR *dirp2); bool sim_dir_exists(const char *dirname); + +struct dirinfo sim_dir_get_info(DIR *dirp, struct sim_dirent *entry); +const char* sim_root_realpath(void); #endif /* !DIRFUNCTIONS_DECLARED */ #endif /* _FILESYSTEM_SIM_H__DIR_H_ */ diff --git a/uisimulator/common/stubs.c b/uisimulator/common/stubs.c index cadead5c70..1823461709 100644 --- a/uisimulator/common/stubs.c +++ b/uisimulator/common/stubs.c @@ -202,6 +202,18 @@ bool usb_charging_enable(bool on) } #endif +#if defined(HAVE_HOST_USB_AUDIO) +void usb_set_audio(int value) +{ + (void)value; +} + +bool usb_audio_get_active(void) +{ + return false; +} +#endif + #ifdef HAVE_REMOTE_LCD_TICKING void lcd_remote_emireduce(bool state) { @@ -214,11 +226,6 @@ void lcd_set_contrast( int x ) (void)x; } -void mpeg_set_pitch(int pitch) -{ - (void)pitch; -} - void cpu_sleep(bool enabled) { (void)enabled; diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index fc771a093d..6563445511 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -124,6 +124,38 @@ else() set(QUAZIP_LIBRARY quazip) endif() +# Setup code signing for macOS +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + # Signing the binaries + set(CODESIGN_IDENTITY "-" CACHE STRING + "Code signing identity to use for macOS signing") + + if(DEFINED ENV{CODESIGN_IDENTITY}) + set(CODESIGN_IDENTITY "$ENV{CODESIGN_IDENTITY}") + endif() + + if("${CODESIGN_IDENTITY}" STREQUAL "-") + message("-- Using ad-hoc Apple code signing") + else() + message("-- Using Apple code signing identity: ${CODESIGN_IDENTITY}") + endif() + + # Submitting the .dmg file to Apple's service to get a + # notarization ticket for distributing outside the App store + set(KEYCHAIN_PROFILE "" CACHE STRING + "Apple Keychain profile name to use for notarization") + + if(DEFINED ENV{KEYCHAIN_PROFILE}) + set(KEYCHAIN_PROFILE "$ENV{KEYCHAIN_PROFILE}") + endif() + + if("${KEYCHAIN_PROFILE}" STREQUAL "") + message("-- Skipping Apple notarization") + else() + message("-- Using Apple notarization keychain profile: ${KEYCHAIN_PROFILE}") + endif() +endif() + add_executable(ipodpatcher-bin ipodpatcher/main.c ipodpatcher/ipodpatcher_aupd.c @@ -133,6 +165,13 @@ target_compile_definitions(ipodpatcher-bin PRIVATE VERSION="none") set_target_properties(ipodpatcher-bin PROPERTIES OUTPUT_NAME ipodpatcher) if(APPLE) target_link_libraries(ipodpatcher-bin ${FRAMEWORK_IOKIT} ${FRAMEWORK_COREFOUNDATION}) + + add_custom_command(TARGET ipodpatcher-bin POST_BUILD + COMMAND codesign --force --options runtime + --sign "${CODESIGN_IDENTITY}" + "$" + VERBATIM + ) endif() add_library(ipodpatcher @@ -164,6 +203,15 @@ target_link_libraries(sansapatcher-bin sansapatcher) target_compile_definitions(sansapatcher-bin PRIVATE VERSION="none") set_target_properties(sansapatcher-bin PROPERTIES OUTPUT_NAME sansapatcher) +if(APPLE) + add_custom_command(TARGET sansapatcher-bin POST_BUILD + COMMAND codesign --force --options runtime + --sign "${CODESIGN_IDENTITY}" + "$" + VERBATIM + ) +endif() + add_library(sansapatcher sansapatcher/parttypes.h sansapatcher/sansaio.h diff --git a/utils/cmake/deploy.cmake b/utils/cmake/deploy.cmake index ff21da4444..ce4d65b956 100644 --- a/utils/cmake/deploy.cmake +++ b/utils/cmake/deploy.cmake @@ -94,8 +94,6 @@ endif() # MacOS: Build dmg if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") - set(CODESIGN_IDENTITY "-" CACHE STRING - "Code signing identity to use for macOS signing") set(DMGBUILD_STAMP ${CMAKE_BINARY_DIR}/dmgbuild.stamp) add_custom_command( COMMENT "Setting up dmgbuild virtualenv" @@ -136,23 +134,51 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") set(_deploy_deps "${_deploy_deps};deploy_${deploy_TARGET}_${_deploy_exe_tgt}") endforeach() + set(_dmg "${CMAKE_BINARY_DIR}/${deploy_TARGET}.dmg") + add_custom_command( # TODO: find a better way to figure the app bundle name. - OUTPUT ${CMAKE_BINARY_DIR}/${deploy_TARGET}.dmg - COMMENT "Running macdeployqt and creating dmg ${deploy_TARGET}" - COMMAND ${MACDEPLOYQT_EXECUTABLE} ${deploy_TARGET}.app + OUTPUT "${_dmg}" + COMMENT "Running macdeployqt and creating ${deploy_TARGET}.dmg" + COMMAND "${MACDEPLOYQT_EXECUTABLE}" "${deploy_TARGET}.app" - COMMAND codesign --force --deep --sign "${CODESIGN_IDENTITY}" ${deploy_TARGET}.app + COMMAND codesign --force --options runtime --deep + --sign "${CODESIGN_IDENTITY}" "${deploy_TARGET}.app" - COMMAND ${DMGBUILD} -s ${deploy_DMGBUILDCFG} - -Dappbundle=${deploy_TARGET}.app - ${deploy_TARGET} ${CMAKE_BINARY_DIR}/${deploy_TARGET}.dmg + COMMAND ${DMGBUILD} -s "${deploy_DMGBUILDCFG}" + -Dappbundle="${deploy_TARGET}.app" + "${deploy_TARGET}" "${_dmg}" DEPENDS ${deploy_TARGET} dmgbuild_venv ${_deploy_deps} ) - add_custom_target(deploy_${deploy_TARGET} - DEPENDS ${CMAKE_BINARY_DIR}/${deploy_TARGET}.dmg) + + if("${KEYCHAIN_PROFILE}" STREQUAL "") + add_custom_target(deploy_${deploy_TARGET} + DEPENDS "${_dmg}") + else() + set(_notarized_stamp "${_dmg}.notarized") + + add_custom_command( + OUTPUT "${_notarized_stamp}" + COMMENT "Submitting ${deploy_TARGET}.dmg for notarization" + + COMMAND xcrun notarytool submit "${_dmg}" + --keychain-profile "${KEYCHAIN_PROFILE}" + --wait + + COMMAND xcrun stapler staple "${_dmg}" + + COMMAND "${CMAKE_COMMAND}" -E touch "${_notarized_stamp}" + + DEPENDS "${_dmg}" + VERBATIM + ) + + add_custom_target(deploy_${deploy_TARGET} + DEPENDS "${_notarized_stamp}") + endif() + add_dependencies(deploy deploy_${deploy_TARGET}) endfunction() endif() diff --git a/utils/hwstub/include/hwstub_gdb.hpp b/utils/hwstub/include/hwstub_gdb.hpp new file mode 100644 index 0000000000..4a9da61de3 --- /dev/null +++ b/utils/hwstub/include/hwstub_gdb.hpp @@ -0,0 +1,112 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2016 by Amaury Pouly + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#ifndef __HWSTUB_GDB_HPP__ +#define __HWSTUB_GDB_HPP__ + +#include "hwstub.hpp" +#include "hwstub_net.hpp" + +namespace hwstub { +namespace gdb { + + +/** GDB context + * + * A GDB context connects to a GDB server and has only one device. */ +class context : public hwstub::context +{ + friend class device; + friend class handle; +protected: + context(int socke_fd); +public: + virtual ~context(); + /** Create a context from an existing socket */ + static std::shared_ptr create(int socket_fd); + /** Create a TCP socket context with a domain name and a port. If port is empty, + * a default port is used. */ + static std::shared_ptr create_tcp(const std::string& domain, + const std::string& port, std::string *error = nullptr); + +protected: + /** Send a message to the server. Context will always serialize calls to send() + * so there is no need to worry about concurrency issues. */ + error send(void *buffer, size_t& sz); + /** Receive a message from the server, sz is updated with the received size. + * Context will always serialize calls to recv() so there is no need to + * worry about concurrency issues. */ + error recv(void *buffer, size_t& sz); + /* NOTE ctx_dev_t is always 0 */ + virtual error fetch_device_list(std::vector& list, void*& ptr); + virtual void destroy_device_list(void *ptr); + virtual error create_device(ctx_dev_t dev, std::shared_ptr& hwdev); + virtual bool match_device(ctx_dev_t dev, std::shared_ptr hwdev); + + hwstub::net::socket_connection m_conn; +}; + +/** GDB device + * + * Device based on gdb context. */ +class device : public hwstub::device +{ + friend class context; /* for ctor */ +protected: + device(std::shared_ptr ctx); +public: + virtual ~device(); + +protected: + virtual error open_dev(std::shared_ptr& handle); + virtual bool has_multiple_open() const; +}; + +/** GDB handle + * + * Handle based on gdb device. */ +class handle : public hwstub::handle +{ + friend class device; /* for ctor */ +protected: + handle(std::shared_ptr dev); +public: + virtual ~handle(); + +protected: + virtual error read_dev(uint32_t addr, void *buf, size_t& sz, bool atomic); + virtual error write_dev(uint32_t addr, const void *buf, size_t& sz, bool atomic); + virtual error get_dev_desc(uint16_t desc, void *buf, size_t& buf_sz); + virtual error get_dev_log(void *buf, size_t& buf_sz); + virtual error exec_dev(uint32_t addr, uint16_t flags); + virtual error status() const; + virtual size_t get_buffer_size(); + +protected: + struct hwstub_version_desc_t m_desc_version; + struct hwstub_layout_desc_t m_desc_layout; + struct hwstub_target_desc_t m_desc_target; +}; + +} // namespace virt +} // namespace hwstub + +#endif /* __HWSTUB_GDB_HPP__ */ + diff --git a/utils/hwstub/include/hwstub_net.hpp b/utils/hwstub/include/hwstub_net.hpp index 72d73e83e5..6ea36af0eb 100644 --- a/utils/hwstub/include/hwstub_net.hpp +++ b/utils/hwstub/include/hwstub_net.hpp @@ -29,6 +29,58 @@ namespace hwstub { namespace net { +/** Net helper + * + * This class provides helpers to implement a socket based context. It abstracts + * send/recv to return sensible error codes, print debug messages, and has helpers + * to create unix and tcp sockets. + * + * NOTE: this class is NOT thread-safe */ +class socket_connection +{ +public: + socket_connection(); + ~socket_connection(); + /** Return socket file descriptor, or -1 if none */ + int get_socket_fd(); + /** Init a socket connection with an existing file descriptor. Note that the + * file descriptor will be closed when the class will be destroyed or init + * again. If a previous socket was associated with this class before, it will + * be closed. */ + void init(int socket_fd); + /** Close the socket connection now */ + void close(); + /** Create a TCP connection with a domain name and a port. + * In case of error, return -1 */ + static int create_tcp(const std::string& domain, + const std::string& port, std::string *error = nullptr); + /** Init a UNIX socket connection with a file system path (see man for details) + * In case of error, the previous connection is NOT closed and + * the function returns false, otherwise it returns true. */ + static int create_unix(const std::string& path, + std::string *error = nullptr); + /** Init a UNIX socket connection with an abstract name (see man for details) + * In case of error, the previous connection is NOT closed and + * the function returns false, otherwise it returns true. */ + static int create_unix_abstract(const std::string& path, + std::string *error = nullptr); + + /** Useful functions for network byte order conversion */ + static uint32_t to_net_order(uint32_t u); + static uint32_t from_net_order(uint32_t u); + + /** set operation timeout */ + void set_timeout(std::chrono::milliseconds ms); + + /** Send a message to the server. */ + error send(void *buffer, size_t& sz, std::ostream& debug = cnull); + /** Receive a message from the server, sz is updated with the received size. */ + error recv(void *buffer, size_t& sz, std::ostream& debug = cnull); + +protected: + int m_socketfd; /* socket file descriptor */ +}; + /** Net context * * A socket context provides access to another context through a network. This @@ -56,6 +108,7 @@ public: /** Create a UNIX socket context with an abstract name (see man for details) */ static std::shared_ptr create_unix_abstract(const std::string& path, std::string *error = nullptr); + /** Useful functions for network byte order conversion */ uint32_t to_net_order(uint32_t u); uint32_t from_net_order(uint32_t u); @@ -112,6 +165,7 @@ protected: virtual void destroy_device_list(void *ptr); virtual error create_device(ctx_dev_t dev, std::shared_ptr& hwdev); virtual bool match_device(ctx_dev_t dev, std::shared_ptr hwdev); + static inline int hwstub_ver(int major, int minor) { return major * 100 + minor; } enum class state { @@ -122,6 +176,7 @@ protected: state m_state; /* client state */ error m_error; /* error state for DEAD */ + int m_ver; /* Server revision (uses hwstub_ver()) */ }; /** Socket based net context @@ -142,7 +197,7 @@ protected: virtual error send(void *buffer, size_t& sz); virtual error recv(void *buffer, size_t& sz); - int m_socketfd; /* socket file descriptor */ + socket_connection m_conn; }; diff --git a/utils/hwstub/lib/hwstub_gdb.cpp b/utils/hwstub/lib/hwstub_gdb.cpp new file mode 100644 index 0000000000..1c1b453f6c --- /dev/null +++ b/utils/hwstub/lib/hwstub_gdb.cpp @@ -0,0 +1,224 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2016 by Amaury Pouly + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#include "hwstub_gdb.hpp" +#include +#include + +namespace hwstub { +namespace gdb { + +/** + * Context + */ +context::context(int socket_fd) +{ + m_conn.init(socket_fd); +} + +context::~context() +{ +} + +std::shared_ptr context::create(int socket_fd) +{ + // NOTE: can't use make_shared() because of the protected ctor */ + return std::shared_ptr(new context(socket_fd)); +} + +std::shared_ptr context::create_tcp(const std::string& domain, + const std::string& port, std::string *error) +{ + int fd = hwstub::net::socket_connection::create_tcp(domain, port, error); + if(fd >= 0) + return context::create(fd); + else + return std::shared_ptr(); +} + +error context::fetch_device_list(std::vector& list, void*& ptr) +{ + (void) ptr; + list.resize(1); + list[0] = 0; + return error::SUCCESS; +} + +void context::destroy_device_list(void *ptr) +{ + (void) ptr; +} + +error context::create_device(ctx_dev_t dev, std::shared_ptr& hwdev) +{ + /* there is only one device, so we don't use dev */ + (void) dev; + // NOTE: can't use make_shared() because of the protected ctor */ + hwdev.reset(new device(shared_from_this())); + return error::SUCCESS; +} + +bool context::match_device(ctx_dev_t dev, std::shared_ptr hwdev) +{ + (void) dev; + (void) hwdev; + /* there is only one device, so they all match */ + return true; +} + +/** + * Device + */ +device::device(std::shared_ptr ctx) + :hwstub::device(ctx) +{ +} + +device::~device() +{ +} + +error device::open_dev(std::shared_ptr& h) +{ + // NOTE: can't use make_shared() because of the protected ctor */ + h.reset(new handle(shared_from_this())); + return error::SUCCESS; +} + +bool device::has_multiple_open() const +{ + /* we want to serialize accesses, otherwise multiple concurrent messages to + * the server over the same connection will confuse the server */ + return false; +} + +/** + * Handle + */ +handle::handle(std::shared_ptr dev) + :hwstub::handle(dev) +{ + m_desc_version.bLength = sizeof(m_desc_version); + m_desc_version.bDescriptorType = HWSTUB_DT_VERSION; + m_desc_version.bMajor = HWSTUB_VERSION_MAJOR; + m_desc_version.bMinor = HWSTUB_VERSION_MINOR; + m_desc_version.bRevision = 0; + + m_desc_layout.bLength = sizeof(m_desc_layout); + m_desc_layout.bDescriptorType = HWSTUB_DT_LAYOUT; + m_desc_layout.dCodeStart = 0; + m_desc_layout.dCodeSize = 0; + m_desc_layout.dStackStart = 0; + m_desc_layout.dStackSize = 0; + m_desc_layout.dBufferStart = 0; + m_desc_layout.dBufferSize = 1; + + m_desc_target.bLength = sizeof(m_desc_target); + m_desc_target.bDescriptorType = HWSTUB_DT_TARGET; + m_desc_target.dID = HWSTUB_TARGET_UNK; + strncpy(m_desc_target.bName, "GDB server", sizeof(m_desc_target.bName)); + m_desc_target.bName[sizeof(m_desc_target.bName) - 1] = 0; +} + +handle::~handle() +{ +} + + +error handle::read_dev(uint32_t addr, void *buf, size_t& sz, bool atomic) +{ + std::shared_ptr hctx = get_device()->get_context(); + if(!hctx) + return error::NO_CONTEXT; + context *ctx = dynamic_cast(hctx.get()); + ctx->debug() << "[gdb::handle] --> READ(0x" << std::hex + << addr << "," << sz << "," << atomic << ")\n"; + ctx->debug() << "[gdb::handle] <-- READ unimplemented\n"; + /* implement read here using ctx->send() and ctx->recv() */ + (void) addr; + (void) buf; + (void) sz; + (void) atomic; + return error::DUMMY; +} + +error handle::write_dev(uint32_t addr, const void *buf, size_t& sz, bool atomic) +{ + std::shared_ptr hctx = get_device()->get_context(); + if(!hctx) + return error::NO_CONTEXT; + context *ctx = dynamic_cast(hctx.get()); + ctx->debug() << "[gdb::handle] --> WRITE(0x" << std::hex + << addr << "," << sz << "," << atomic << ")\n"; + ctx->debug() << "[gdb::handle] <-- WRITE unimplemented\n"; + /* implement read here using ctx.send() and ctx.recv() */ + (void) addr; + (void) buf; + (void) sz; + (void) atomic; + return error::DUMMY; +} + +error handle::get_dev_desc(uint16_t desc, void *buf, size_t& buf_sz) +{ + void *p = nullptr; + switch(desc) + { + case HWSTUB_DT_VERSION: p = &m_desc_version; break; + case HWSTUB_DT_LAYOUT: p = &m_desc_layout; break; + case HWSTUB_DT_TARGET: p = &m_desc_target; break; + default: break; + } + if(p == nullptr) + return error::ERROR; + /* size is in the bLength field of the descriptor */ + size_t desc_sz = *(uint8_t *)p; + buf_sz = std::min(buf_sz, desc_sz); + memcpy(buf, p, buf_sz); + return error::SUCCESS; +} + +error handle::get_dev_log(void *buf, size_t& buf_sz) +{ + (void) buf; + (void) buf_sz; + return error::DUMMY; +} + +error handle::exec_dev(uint32_t addr, uint16_t flags) +{ + (void) addr; + (void) flags; + return error::DUMMY; +} + +error handle::status() const +{ + return hwstub::handle::status(); +} + +size_t handle::get_buffer_size() +{ + return 2048; +} + +} // namespace gdb +} // namespace net + diff --git a/utils/hwstub/lib/hwstub_net.cpp b/utils/hwstub/lib/hwstub_net.cpp index 89994cdf7a..7009295bd8 100644 --- a/utils/hwstub/lib/hwstub_net.cpp +++ b/utils/hwstub/lib/hwstub_net.cpp @@ -29,39 +29,6 @@ namespace hwstub { namespace net { -/** - * Context - */ -context::context() - :m_state(state::HELLO), m_error(error::SUCCESS) -{ -} - -context::~context() -{ -} - -std::shared_ptr context::create_socket(int socket_fd) -{ - // NOTE: can't use make_shared() because of the protected ctor */ - return std::shared_ptr(new socket_context(socket_fd)); -} - -std::string context::default_unix_path() -{ - return "hwstub"; -} - -std::string context::default_tcp_domain() -{ - return "localhost"; -} - -std::string context::default_tcp_port() -{ - return "6666"; -} - namespace { /* len is the total length, including a 0 character if any */ @@ -171,10 +138,163 @@ namespace } } +/** + * Socket connection + */ +socket_connection::socket_connection() + :m_socketfd(-1) +{ +} + +socket_connection::~socket_connection() +{ + close(); +} + +int socket_connection::get_socket_fd() +{ + return m_socketfd; +} + +void socket_connection::init(int socket_fd) +{ + close(); + m_socketfd = socket_fd; +} + +void socket_connection::close() +{ + if(m_socketfd >= 0) + ::close(m_socketfd); + m_socketfd = -1; +} + +int socket_connection::create_tcp(const std::string& domain, + const std::string& port, std::string *error) +{ + return create_tcp_low(domain, port, false, error); +} + +int socket_connection::create_unix(const std::string& path, + std::string *error) +{ + return create_unix_low(false, path.c_str(), path.size() + 1, true, error); +} + +int socket_connection::create_unix_abstract(const std::string& path, + std::string *error) +{ + std::string fake_path = "#" + path; /* the # will be overriden by 0 */ + return create_unix_low(true, fake_path.c_str(), fake_path.size(), true, error); +} + +uint32_t socket_connection::to_net_order(uint32_t u) +{ + return htonl(u); +} + +uint32_t socket_connection::from_net_order(uint32_t u) +{ + return ntohl(u); +} + +void socket_connection::set_timeout(std::chrono::milliseconds ms) +{ + struct timeval tv; + tv.tv_usec = 1000 * (ms.count() % 1000); + tv.tv_sec = ms.count() / 1000; + /* set timeout for the client operations */ + setsockopt(m_socketfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv)); + setsockopt(m_socketfd, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(tv)); +} + +error socket_connection::send(void *buffer, size_t& sz, std::ostream& debug) +{ + debug << "[net::sock] send(" << sz << "): "; + int ret = ::send(m_socketfd, buffer, sz, MSG_NOSIGNAL); + if(ret >= 0) + { + debug << "good(" << ret << ")\n"; + sz = (size_t)ret; + return error::SUCCESS; + } + /* convert some errors */ + debug << "fail(" << errno << "," << strerror(errno) << ")\n"; + switch(errno) + { +#if EAGAIN != EWOULDBLOCK + case EAGAIN: +#endif + case EWOULDBLOCK: return error::TIMEOUT; + case ECONNRESET: case EPIPE: return error::SERVER_DISCONNECTED; + default: return error::NET_ERROR; + } +} + +error socket_connection::recv(void *buffer, size_t& sz, std::ostream& debug) +{ + debug << "[net::sock] recv(" << sz << "): "; + int ret = ::recv(m_socketfd, buffer, sz, MSG_WAITALL); + if(ret > 0) + { + debug << "good(" << ret << ")\n"; + sz = (size_t)ret; + return error::SUCCESS; + } + if(ret == 0) + { + debug << "disconnected\n"; + return error::SERVER_DISCONNECTED; + } + debug << "fail(" << errno << "," << strerror(errno) << ")\n"; + switch(errno) + { +#if EAGAIN != EWOULDBLOCK + case EAGAIN: +#endif + case EWOULDBLOCK: return error::TIMEOUT; + default: return error::NET_ERROR; + } +} + + +/** + * Context + */ +context::context() + :m_state(state::HELLO), m_error(error::SUCCESS) +{ +} + +context::~context() +{ +} + +std::shared_ptr context::create_socket(int socket_fd) +{ + // NOTE: can't use make_shared() because of the protected ctor */ + return std::shared_ptr(new socket_context(socket_fd)); +} + +std::string context::default_unix_path() +{ + return "hwstub"; +} + +std::string context::default_tcp_domain() +{ + return "localhost"; +} + +std::string context::default_tcp_port() +{ + return "6666"; +} + std::shared_ptr context::create_tcp(const std::string& domain, const std::string& port, std::string *error) { - int fd = create_tcp_low(domain, port, false, error); + int fd = socket_connection::create_tcp(domain, port, error); if(fd >= 0) return context::create_socket(fd); else @@ -183,7 +303,7 @@ std::shared_ptr context::create_tcp(const std::string& domain, std::shared_ptr context::create_unix(const std::string& path, std::string *error) { - int fd = create_unix_low(false, path.c_str(), path.size() + 1, true, error); + int fd = socket_connection::create_unix(path, error); if(fd >= 0) return context::create_socket(fd); else @@ -192,8 +312,7 @@ std::shared_ptr context::create_unix(const std::string& path, std::stri std::shared_ptr context::create_unix_abstract(const std::string& path, std::string *error) { - std::string fake_path = "#" + path; /* the # will be overriden by 0 */ - int fd = create_unix_low(true, fake_path.c_str(), fake_path.size(), true, error); + int fd = socket_connection::create_unix_abstract(path, error); if(fd >= 0) return context::create_socket(fd); else @@ -264,12 +383,12 @@ bool context::match_device(ctx_dev_t dev, std::shared_ptr hwdev) uint32_t context::to_net_order(uint32_t u) { - return htonl(u); + return socket_connection::to_net_order(u); } uint32_t context::from_net_order(uint32_t u) { - return ntohl(u); + return socket_connection::from_net_order(u); } error context::send_cmd(uint32_t cmd, uint32_t args[HWSTUB_NET_ARGS], uint8_t *send_data, @@ -391,7 +510,7 @@ error context::send_cmd(uint32_t cmd, uint32_t args[HWSTUB_NET_ARGS], uint8_t *s } } /* copy data if user want it */ - if(recv_data) + if(recv_data && recv_size) { if(*recv_data == nullptr) { @@ -437,14 +556,25 @@ void context::delayed_init() return; } /* check the server is running the same version */ - debug() << "[net::ctx] <-- HELLO " << ((args[0] & 0xff00) >> 8) << "." << (args[0] & 0xff) << ""; - if(args[0] != (HWSTUB_VERSION_MAJOR << 8 | HWSTUB_VERSION_MINOR)) + int serv_maj = (args[0] >> 8) & 0xff; + int serv_min = args[0] & 0xff; + debug() << "[net::ctx] <-- HELLO " << serv_maj << "." << serv_min << ""; + + m_ver = hwstub_ver(serv_maj, serv_min); + /* Major mismatch -> error */ + if(serv_maj != HWSTUB_VERSION_MAJOR) { - debug() << " (mismatch)\n"; + debug() << " (major version mismatch)\n"; m_state = state::DEAD; m_error = error::SERVER_MISMATCH; } - debug() << " (good)\n"; + else + debug() << " (good)\n"; + /* Minor mismatch -> warning */ + if(serv_min < HWSTUB_VERSION_MINOR) + debug() << "[net::ctx] Some operations might not be supported by the server.\n"; + else if(serv_min > HWSTUB_VERSION_MINOR) + debug() << "[net::ctx] Some operations might not be supported by this client.\n"; /* good, we can now send commands */ m_state = state::IDLE; } @@ -481,74 +611,29 @@ void context::stop_context() * Socket context */ socket_context::socket_context(int socket_fd) - :m_socketfd(socket_fd) { + m_conn.init(socket_fd); set_timeout(std::chrono::milliseconds(1000)); } socket_context::~socket_context() { stop_context(); - close(m_socketfd); } void socket_context::set_timeout(std::chrono::milliseconds ms) { - struct timeval tv; - tv.tv_usec = 1000 * (ms.count() % 1000); - tv.tv_sec = ms.count() / 1000; - /* set timeout for the client operations */ - setsockopt(m_socketfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv)); - setsockopt(m_socketfd, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(tv)); + m_conn.set_timeout(ms); } error socket_context::send(void *buffer, size_t& sz) { - debug() << "[net::ctx::sock] send(" << sz << "): "; - int ret = ::send(m_socketfd, buffer, sz, MSG_NOSIGNAL); - if(ret >= 0) - { - debug() << "good(" << ret << ")\n"; - sz = (size_t)ret; - return error::SUCCESS; - } - /* convert some errors */ - debug() << "fail(" << errno << "," << strerror(errno) << ")\n"; - switch(errno) - { -#if EAGAIN != EWOULDBLOCK - case EAGAIN: -#endif - case EWOULDBLOCK: return error::TIMEOUT; - case ECONNRESET: case EPIPE: return error::SERVER_DISCONNECTED; - default: return error::NET_ERROR; - } + return m_conn.send(buffer, sz, debug()); } error socket_context::recv(void *buffer, size_t& sz) { - debug() << "[net::ctx::sock] recv(" << sz << "): "; - int ret = ::recv(m_socketfd, buffer, sz, MSG_WAITALL); - if(ret > 0) - { - debug() << "good(" << ret << ")\n"; - sz = (size_t)ret; - return error::SUCCESS; - } - if(ret == 0) - { - debug() << "disconnected\n"; - return error::SERVER_DISCONNECTED; - } - debug() << "fail(" << errno << "," << strerror(errno) << ")\n"; - switch(errno) - { -#if EAGAIN != EWOULDBLOCK - case EAGAIN: -#endif - case EWOULDBLOCK: return error::TIMEOUT; - default: return error::NET_ERROR; - } + return m_conn.recv(buffer, sz, debug()); } /** @@ -994,16 +1079,22 @@ error server::handle_cmd(client_state *state, uint32_t cmd, uint32_t args[HWSTUB /* HELLO */ if(cmd == HWSERVER_HELLO) { - debug() << "[net::srv::cmd] --> HELLO " << ((args[0] & 0xff00) >> 8) - << "." << (args[0] & 0xff); - if(args[0] != (HWSTUB_VERSION_MAJOR << 8 | HWSTUB_VERSION_MINOR)) + int cli_maj = (args[0] >> 8) & 0xff; + int cli_min = args[0] & 0xff; + debug() << "[net::srv::cmd] --> HELLO " << cli_maj << "." << cli_min; + if(cli_maj != HWSTUB_VERSION_MAJOR) { - debug() << " (mismatch)\n"; + debug() << " (major version mismatch)\n"; return error::ERROR; } debug() << " (good)\n"; debug() << "[net::srv::cmd] <-- HELLO " << HWSTUB_VERSION_MAJOR << "." << HWSTUB_VERSION_MINOR << "\n"; + /* Minor mismatch -> warning */ + if(cli_min < HWSTUB_VERSION_MINOR) + debug() << "[net::ctx] Some operations might not be supported by the client.\n"; + else if(cli_min > HWSTUB_VERSION_MINOR) + debug() << "[net::ctx] Some operations might not be supported by this server.\n"; /* send HELLO with our version */ args[0] = HWSTUB_VERSION_MAJOR << 8 | HWSTUB_VERSION_MINOR; return error::SUCCESS; @@ -1221,7 +1312,6 @@ error server::handle_cmd(client_state *state, uint32_t cmd, uint32_t args[HWSTUB error err = it->second->write(addr, recv_data, recv_size, !!(flags & HWSERVER_RW_ATOMIC)); if(err != error::SUCCESS) { - delete[] send_data; debug() << "[net::srv::cmd] cannot write: " << error_string(err) << "\n"; debug() << "[net::srv::cmd] <-- WRITE (error)\n"; return err; diff --git a/utils/hwstub/lib/hwstub_uri.cpp b/utils/hwstub/lib/hwstub_uri.cpp index da59671540..578f30b2ab 100644 --- a/utils/hwstub/lib/hwstub_uri.cpp +++ b/utils/hwstub/lib/hwstub_uri.cpp @@ -21,6 +21,7 @@ #include "hwstub_usb.hpp" #include "hwstub_virtual.hpp" #include "hwstub_net.hpp" +#include "hwstub_gdb.hpp" #include namespace hwstub { @@ -41,6 +42,8 @@ void print_usage(FILE *f, bool client, bool server) if(client) fprintf(f, " usb USB context (using libusb)\n"); fprintf(f, " tcp TCP context\n"); + if(client) + fprintf(f, " gdb A GDB context over tcp\n"); fprintf(f, " unix Local unix domain context\n"); if(client) fprintf(f, " virt A virtual context (testing and debugging mostly)\n"); @@ -63,6 +66,8 @@ void print_usage(FILE *f, bool client, bool server) fprintf(f, "in which case the domain must start with a '#':\n"); fprintf(f, " unix:///path/to/socket\n"); fprintf(f, " unix://#hwstub\n"); + fprintf(f, "When creating a GDB context, the domain and port are the usual TCP parameters:\n"); + fprintf(f, " gdb://localhost:4141\n"); if(client) { fprintf(f, "When creating a virtual context, the domain must contain a specification of the devices.\n"); @@ -288,6 +293,10 @@ std::shared_ptr create_context(const uri& uri, std::string *error) { return hwstub::net::context::create_tcp(uri.domain(), uri.port(), error); } + else if(uri.scheme() == "gdb") + { + return hwstub::gdb::context::create_tcp(uri.domain(), uri.port(), error); + } else if(uri.scheme() == "unix") { /* port must be empty */ diff --git a/utils/hwstub/tools/hwstub_load.cpp b/utils/hwstub/tools/hwstub_load.cpp index 65ce64b05c..8db1963174 100644 --- a/utils/hwstub/tools/hwstub_load.cpp +++ b/utils/hwstub/tools/hwstub_load.cpp @@ -107,12 +107,11 @@ void usage(void) { printf("usage: hwstub_load [options] \n"); printf("options:\n"); - printf(" --help/-? Display this help\n"); + printf(" --help/-h Display this help\n"); printf(" --quiet/-q Quiet output\n"); printf(" --verbose/-v Verbose output\n"); printf(" --type/-t Override file type\n"); printf(" --dev/-d Device URI (see below)\n"); - printf(" --verbose/-v Display debug output\n"); printf(" --noload Skip loading stage and only execute the given address\n"); printf(" --noexec Skip execute stage and only load data the given address\n"); printf("file types:\n"); @@ -123,7 +122,7 @@ void usage(void) for(int i = 0; players[i].name; i++) printf(" %s", players[i].name); printf("\n"); - //hwstub::usage_uri(stdout); + hwstub::uri::print_usage(stdout, true, false); exit(1); } @@ -181,6 +180,7 @@ int main(int argc, char **argv) break; case 'v': verbose = true; + break; case 'e': no_load = true; break; diff --git a/utils/hwstub/tools/hwstub_shell.cpp b/utils/hwstub/tools/hwstub_shell.cpp index 9fda72981f..4f94be08ab 100644 --- a/utils/hwstub/tools/hwstub_shell.cpp +++ b/utils/hwstub/tools/hwstub_shell.cpp @@ -1341,7 +1341,7 @@ void usage(void) printf(" --debug-rw Print read/write\n"); printf("Relative order of -e and -f commands are preserved.\n"); printf("Unless specified, the shell will load soc desc in regtools/desc/\n"); - //usage_uri(stdout); + hwstub::uri::print_usage(stdout, true, false); exit(1); } diff --git a/utils/ingenic_tools/usbboot.c b/utils/ingenic_tools/usbboot.c index 208888d3d4..e02941469c 100644 --- a/utils/ingenic_tools/usbboot.c +++ b/utils/ingenic_tools/usbboot.c @@ -103,7 +103,7 @@ void jz_get_cpu_info(void) int ret = libusb_control_transfer(g_usb_dev, LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, VR_GET_CPU_INFO, 0, 0, buf, 8, 1000); - if(ret != 0) + if(ret < 0) die("Can't get CPU info: %d", ret); buf[8] = 0; @@ -172,7 +172,7 @@ void jz_download(const char* filename) int ret = libusb_control_transfer(g_usb_dev, \ LIBUSB_ENDPOINT_OUT|LIBUSB_REQUEST_TYPE_VENDOR|LIBUSB_RECIPIENT_DEVICE, \ VR_##type, param >> 16, param & 0xffff, NULL, 0, 1000); \ - if(ret != 0) \ + if(ret < 0) \ die("Request " #type " failed: %d", ret); \ } diff --git a/utils/ipod/flashsplit/README b/utils/ipod/flashsplit/README index bca13f9c04..814a576ab0 100644 --- a/utils/ipod/flashsplit/README +++ b/utils/ipod/flashsplit/README @@ -1,8 +1,8 @@ flashsplit ---------- -A simple utility to split a dump of the NOR flash on an ipod. +A simple utility to split a dump of the NOR flash on an iPod. -Should work on all PortalPlayer-based ipods. +Should work on all PortalPlayer-based iPods. See http://www.rockbox.org/wiki/IpodFlash for more information. diff --git a/utils/ipodnano3g/README b/utils/ipodnano3g/README new file mode 100644 index 0000000000..08127a315d --- /dev/null +++ b/utils/ipodnano3g/README @@ -0,0 +1,94 @@ +# iPod Nano 3G storage validation + +Copyright (C) 2026 Andrew Rice. + +This directory contains the host-side evidence and reproducibility tools +used to validate the Nano 3G NAND and FTL implementation. The source files +here are distributed under the same GNU General Public License, version 2 +or later, as the Rockbox source tree. + +No Apple firmware, sequencer binary, NAND image, user data or block trace is +included. The tests that need those inputs require the person running them +to supply images obtained from their own device. + +## FTL tests + +`ftltest` builds `ftl-nano3g.c` against a copy-on-write NAND mock. It expects +two files in a directory named on each test's command line: + +- `nand_data_4banks.bin`: page data from a four-bank A514D3AD device, stored + bank by bank; +- `meta_all.bin`: 16 bytes per physical page, consisting of the three spare + words followed by the signed `nand_read_page()` result. + +The mock maps both inputs privately and cannot modify them. Run: + +``` +cd utils/ipodnano3g/ftltest +make check DUMP=/path/to/dump +make check-crash DUMP=/path/to/dump +``` + +`test_crash` provides deterministic power-cut and torn-write fault injection. +`replay` accepts a separately supplied block trace. + +The tests also take a medium spec in place of a dump directory: +`collected:DIR` is an archive from the NAND check below, and `blank:PRESET` +formats an empty medium in one of the chip table's layouts (see +`ftl_hooks.c`). + +## Validating a chip + +A chip's row in `nand_chip_table[]` +(`firmware/target/arm/s5l8702/ipodnano3g/nand-nano3g.c`) says whether it +has been proven on hardware. Rockbox drives only validated chips: on any +other the bootloader says the NAND is not validated and starts Apple's +firmware. + +Validating one takes, in order: + +1. a check archive from the unit (below), which identifies the chip and + records its structures; +2. `test_ftl collected:DIR` and `test_crash collected:DIR 100 1 3000 0` + from `ftltest`, which mount that medium and exercise writes and power + cuts in its geometry on the host; +3. a write test on the unit itself, with a firmware built + `-DNAND_WRITABLE_ID=0x` (for example `-DNAND_WRITABLE_ID=0xA555D5AD`). + That mounts chips with this id writable without changing the table, so + an owner can copy a tree through Rockbox's USB mode, power off cleanly, + restart and check the files back. + +`-DNAND_TEST_READONLY` does the opposite: it treats every chip as +unvalidated, which is how the refusal path is tested on a validated unit. + +Only then is the row marked validated. + +## NAND check for contributors + +`nandcheck` builds a one-shot image, run from DFU, that identifies an iPod's +NAND chip, tries a read-only mount and serves the raw NAND read-only over +USB; `nandcheck.py collect` turns that into a small archive for validating +the chip with the tests above. Nothing is installed or written. See +`nandcheck/README.md`. + +## Sequencer register comparison + +`regdiff` compares the controller-register writes made by the NAND driver +with Apple's flash-controller sequencer programs. Set `NANO3G_OSOS` to a +decrypted firmware image obtained from the user's own device, then run: + +``` +NANO3G_OSOS=/path/to/osos.bin python3 utils/ipodnano3g/regdiff/regdiff.py +NANO3G_OSOS=/path/to/osos.bin python3 utils/ipodnano3g/regdiff/regdiff_read.py +``` + +The tools read the sequencer programs at their documented IRAM/file offsets; +they do not contain or create copies of those programs. Expected results are +recorded in `RESULTS.md`. + +## Decode notes + +`decode` records the independently decoded call structure, data formats and +sequencer behavior. Addresses identify locations in firmware 1.1.3 so a +reviewer with their own image can reproduce the analysis. Raw firmware and +bulk disassembly listings are deliberately excluded. diff --git a/utils/ipodnano3g/RESULTS.md b/utils/ipodnano3g/RESULTS.md new file mode 100644 index 0000000000..c7e8166ea8 --- /dev/null +++ b/utils/ipodnano3g/RESULTS.md @@ -0,0 +1,75 @@ +# Nano 3G storage validation results + +Results below were recorded on 2026-09-13. Hardware results used a 4 GB Nano +3G with Hynix NAND ID `0xA514D3AD`. + +## Host tests + +- The independent newest-page oracle agreed with `ftl_read()` for all + 1,982,464 logical sectors in the device image. +- Read/write, wear-level, bad-block injection, allocation eligibility, + restore and mock-NAND suites completed without failures. +- Power was cut at 300 operation boundaries in each of 48 deterministic + campaigns. Half modeled a torn page program or half-completed erase. All + acknowledged writes survived each remount and no page was programmed twice + or out of order. +- The 600-file write replay retained 66,388 copy-merge reads and 34,816 + delete-merge reads. Batched reads reduced these to 528 and 280 controller + calls respectively, averaging 125.7 and 124.3 pages per call. +- Driver and emulator register traces were identical in all write scenarios: + one page, single-plane rows, repeated-bank pages, and one through three + two-plane rows. +- Driver and emulator register traces were identical for one read, four-bank + reads, repeated-bank reads and a mixed duplicate-bank split. + +## Hardware tests + +- A complete NAND dump produced 184 VFL context pages that all passed the + format's checksums. +- Rockbox mounted and restored the uncommitted state left by Apple disk mode. +- A 77,067,368-byte, 600-file tree written through Rockbox read back with all + SHA-256 hashes intact through both Rockbox and Apple disk mode. +- The same tree read at 7.550 MB/s through Rockbox USB and 9.745 MB/s through + Apple disk mode. Rockbox wrote and synced it at 0.731 MB/s; Apple wrote and + synced it at 1.026 MB/s. +- A setting survived reset and restore, and Apple firmware subsequently read + the volume successfully. + +Bad-block remapping and forced program/erase failure recovery have extensive +host fault-injection coverage, including validation of the persisted remap +after remount. They have not been deliberately triggered on hardware. The +implemented layout and recovery order follow the decoded Apple FTL/VFL +process documented in `decode/APPLE-FTL-WRITE.md`; that correspondence is +evidence for the implementation, not a substitute for destructive hardware +fault testing. + +## Later additions + +- **NAND check tool update, 2026-09-17.** The rebuilt check image reads a + 4GB unit with no ECC failures or timeouts; two independent collections + from it agree byte for byte. +- **2-CE Micron A5D5D52C, enabled 2026-09-18.** A contributor's check + archive matches the table row exactly, including a derived `vflspares` + of 89. Replayed against the host suite: `test_ftl` agrees with the + oracle on all 1,982,464 sectors; `test_crash` survives 100 power cuts + (172 writes, 6 syncs, 0 sectors wrong). No on-device write test. +- **B614D5EC x2, A5D5D589 x2/x4, 3E94D589 x2, 2026-09-19.** Four + contributors' check archives all match their rows and pass `test_crash` + clean. `test_ftl` passes three outright; A5D5D589 x2 disagrees with the + oracle on two logical pages - confirmed against Apple's own disassembly + (`_FTLRestore`, osos 1.1.3) to be that algorithm's own tie-break between + two competing logs, not a defect (see the note in `ftltest/test_ftl.c`). +- **BA94D598 x4, 2026-09-20.** A contributor's check archive matches the + row exactly (Toshiba, mode 1, vflspares 201 by our own formula). Clean + on both host tests: `test_ftl` agrees with the oracle on all 3,964,928 + sectors; `test_crash` survives 100 power cuts (344 writes, 11 syncs, 0 + sectors wrong). No on-device write test. +- **B614D5EC x4, 2026-09-21.** A contributor's check archive matches the + row exactly (mode 1, vflspares 201 by our own formula). `test_crash` is + clean (100 power cuts, 349 writes, 9 syncs, 0 sectors wrong). `test_ftl` + disagrees with the oracle on two logical pages in one block; traced to a + second, distinct false positive from the one already documented for + A5D5D589 x2 - a closed data block addressed purely by position, with one + stale leftover page - confirmed against the decode notes + (`_FTLRestore`'s "closed blocks -> map" step) and not a defect (see + `ftltest/test_ftl.c`). diff --git a/utils/ipodnano3g/chiptable.py b/utils/ipodnano3g/chiptable.py new file mode 100644 index 0000000000..e8df0f60ce --- /dev/null +++ b/utils/ipodnano3g/chiptable.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 +"""Check nand-nano3g.c's chip table against Apple's and against the FTL. + +Every row of nand_chip_table[] must equal a row of the chip table in a +decrypted Nano 3G firmware 1.1.3 image (NANO3G_OSOS), in the same order, and +the geometry the driver and FTL derive from each row must fit the FTL's +static bounds. Rows the FTL does not mount yet are reported, not failed. +""" +import os +import re +import struct +import sys + +HERE = os.path.dirname(os.path.abspath(__file__)) +ROCKBOX = os.path.abspath(os.path.join(HERE, "..", "..")) +TARGET = ROCKBOX + "/firmware/target/arm/s5l8702/ipodnano3g" +OSOS = os.environ.get("NANO3G_OSOS") + +# Firmware 1.1.3: the table sits at this file offset, 18 rows of 44 bytes +APPLE_TABLE, APPLE_ROWS, APPLE_ROW = 0x9269bc, 18, 44 + + +def ours(): + src = open(TARGET + "/nand-nano3g.c").read() + body = src[src.index("nand_chip_table[] ="):] + body = body[:body.index("};")] + rows = [] + for m in re.finditer(r"\{\s*(0x[0-9A-Fa-f]+),\s*(\d+),\s*(\d+),\s*(\d+)," + r"\s*(\d+),\s*(\d+),\s*(\d+),\s*(true|false)\s*\}", + body): + rows.append(dict(id=int(m.group(1), 16), banks=int(m.group(2)), + mode=int(m.group(3)), blocks=int(m.group(4)), + ppb=int(m.group(5)), pagesize=int(m.group(6)), + user=int(m.group(7)), validated=m.group(8) == "true")) + return rows + + +def apple(): + d = open(OSOS, "rb").read() + rows = [] + for n in range(APPLE_ROWS): + e = d[APPLE_TABLE + n * APPLE_ROW:][:APPLE_ROW] + cid, banks, blocks, ppb, spp = struct.unpack_from(" lim] + mib = sbpages * r["pagesize"] / (1 << 20) + maxpage = nand.get("NAND_MAX_PAGE_SIZE", nand["NAND_PAGE_SIZE"]) + pending = "; page size %d not supported yet" % r["pagesize"] \ + if r["pagesize"] > maxpage else "" + fails += bool(over) + print("%s row %2d mode %2d P %d: %4d sb, %4d logical, %4d pages/sb" + " (%g MiB), vflspares %3d%s%s" % + ("FAIL" if over else "PASS", n, r["mode"], p, nsb, usersb, + sbpages, mib, spares, + "".join("; %s %d > %d" % o for o in over), pending)) + print("ALL PASS" if not fails else "%d FAILED" % fails) + return 1 if fails else 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/utils/ipodnano3g/decode/APPLE-FTL-READ.md b/utils/ipodnano3g/decode/APPLE-FTL-READ.md new file mode 100644 index 0000000000..f0b2e4a75a --- /dev/null +++ b/utils/ipodnano3g/decode/APPLE-FTL-READ.md @@ -0,0 +1,188 @@ +# Apple Nano 3G FTL read path (osos 1.1.3, Whimory 2.1), decoded 2026-09-13 + +Claims use **[measured]**, **[decoded]**, **[inferred]** and **[unknown]** as +defined by the project notes. Addresses are ARM addresses in +`n3g-dump/osos-1.1.3.bin`; the FMSS program is at IRAM address 0x22009030. + +| function | address | identification | +|---|---:|---| +| `FTL_Read` | 0x0803ace4 | **[decoded]** AND dispatcher call and arguments | +| `_FTLRead` | 0x080e3514 | **[decoded]** internal mapping/batched read | +| `VFL_Read` | 0x0805e8e8 | **[decoded]** single-page read and retry | +| `VFL_ReadMultiplePagesInVb` | 0x0805ecc8 | **[decoded]** sequential virtual-block read | +| `VFL_ReadScatteredPagesInVb` | 0x0805f0b8 | **[decoded]** VPN-list read | +| `m1fmssReadScatteredPages` | 0x082d3c78 | **[decoded]** FIL list builder | +| `m1fmssReadSequentialPages` | 0x082d3de0 | **[decoded]** FIL row-list builder | +| FMSS read program | 0x22009030 | **[decoded]** common program used by both FIL calls | + +## FTL_Read and _FTLRead + +`FTL_Read(lpn, count, buffer)` updates read statistics, calls +`_FTLRead(lpn, count, buffer)`, logs a nonzero result, services the refresh +list when it contains more than two entries (or a secondary counter passes +its threshold), and returns `_FTLRead`'s result. The AND call dispatcher at +0x080279bc loads the same three arguments before calling it. **[decoded]** + +`_FTLRead` rejects a null buffer, a zero/out-of-range request, and a buffer +whose address is not four-byte aligned. It splits the request at logical +block boundaries. For each logical block it looks up its log entry. **[decoded]** + +- With no log entry, it issues one `VFL_ReadMultiplePagesInVb(data_vbn, + page_offset, count, buffer, spares, &refresh)` for the whole portion of the + logical block. **[decoded]** +- With a log entry, it resolves every requested LPN to either the log block + page or the data block page, builds a VPN list, and issues one + `VFL_ReadScatteredPagesInVb(vpn_list, count, buffer, spares, &refresh)`. + **[decoded]** +- It then checks every returned 12-byte spare. A written page's stored LPN + must equal the requested LPN. An ECC mark other than 0xff is reported as an + error and the logical block is added to the refresh list. **[decoded]** +- If a multi-page VFL call fails, it retries each page separately through + `VFL_Read`, preserving the page's resolved virtual address. A failing page + or an unacceptable ECC mark makes the overall call fail, but the loop can + continue far enough to report more detail. **[decoded]** + +The merge/copy callers use the same fallback: `_DoSimpleMerge` and the block +copy at 0x080e4030 read `pages_per_superblock / 8` pages with `_FTLRead`; on +failure they retry each page with `_FTLRead(..., 1, ...)`, and put 0x55 in +byte 10 of that page's new 12-byte spare if the retry also fails. The 0x55 is +written with the destination block during the following write run; it is not +written back into the source page. **[decoded]** + +The Nano 2G implementation selects a bank-wide fast path only when aligned +and when no log is present. Nano 3G Apple firmware instead supplies the whole +logical-block portion to one of the two VFL multi-page interfaces, including +mixed data/log runs through the scattered interface. **[decoded]** + +## VFL splitting and error handling + +`VFL_ReadMultiplePagesInVb(vbn, start, count, buffer, spares, &refresh)` maps +the virtual block independently on every bank, then splits the run according +to the four-bank row width: **[decoded]** + +1. A head ending at the next row boundary is sent to the scattered FIL call, + unless it is a complete row. +2. While more than one complete row remains, the largest multiple of four + leaving the final row/tail is sent to the sequential FIL call. +3. The remaining one through four pages are sent to the scattered FIL call. + +Thus a row-aligned run of four pages is deliberately a scattered FIL call; +eight pages become four sequential plus four scattered. This is the same +head / whole rows while more than one row remains / tail policy used by +Apple's VFL write path. Reads have no two-plane eligibility test. **[decoded]** + +For each subcall the VFL advances data by 2048 bytes/page, spare data by 12 +bytes/page, and the virtual-page position by the number completed. It returns +1 only when every FIL subcall returns either 0 or 1; other FIL status values +produce VFL failure (0). The maximum corrected-bit count returned by the FIL +is compared with the device threshold byte at device-info +0x0f; reaching or +exceeding it sets `*refresh = 1`. **[decoded]** + +`VFL_ReadScatteredPagesInVb` maps every input VPN into parallel bank and +physical-page arrays, calls the scattered FIL once for the full list, applies +the same corrected-bit threshold, and normalizes FIL status 0 or 1 to VFL +success 1. **[decoded]** + +`VFL_Read` maps one VPN and calls the mode's single-page FIL read. On the +relevant failures it resets the NAND bank and retries once. Its later path can +schedule a block remap and update per-block read accounting. **[decoded]** + +The refresh list and its handler mutate FTL state and can ultimately cause +NAND writes. Rockbox currently has no equivalent. Porting that mechanism is +outside the read batching itself and requires the user's explicit scope +decision before implementation. **[decoded]** + +## FIL parameter block + +Both FIL functions run program 0x22009030 and write the same sequencer +parameter block. **[decoded]** + +| offset | meaning | +|---:|---| +| `SEQ+0xc04` | program address, 0x22009030 | +| `SEQ+0xd04` | bank count (4 on Nano 3G) | +| `SEQ+0xd0c` | pointer to u32 physical-page list | +| `SEQ+0xd10` | pointer to u32 logical-bank list | +| `SEQ+0xd14` | pointer to bank-to-chip-enable table | +| `SEQ+0xd18` | number of pages | +| `SEQ+0xd1c` | pointer to packed 12-byte spare outputs | +| `SEQ+0xd20` | pointer to the page/chunk DMA-address list | +| `SEQ+0xd24` | pointer to one u32 ECC/result word per page | +| `SEQ+0xd28` | 2KiB units per page (sectors-per-page / 4 = 1 here) | +| `SEQ+0xd48` | base `FMCTRL0` bits from the mode descriptor | +| `SEQ+0xd4c` | additional `FMCTRL0` bits from the mode descriptor | + +The scattered FIL accepts parallel bank/page inputs and expands a contiguous +data buffer into four 512-byte DMA addresses per page. The sequential FIL +accepts one starting physical page per bank and expands it row by row into +the same page and bank lists; its buffer layout is likewise expanded into +four chunk addresses per page. Both call the same completion routine and +then classify the aggregate result at `SEQ+0xc30`. **[decoded]** + +The result classifier treats bit 30 as a hard FIL failure. Bit 29 identifies +the erased/empty-page condition used by a single-page caller. The low byte is +a bitmap of corrected-bit counts seen among the page's four chunks; the +highest set bit becomes the returned maximum corrected-bit count. **[decoded]** + +## FMSS opcodes 0x11, 0x18 and 0x19 + +All occurrences in 0x22009030 pin down the previously unknown operations: +**[decoded]** + +``` +0x11: mem32[rB] = rA +0x18: rA = seq32[rB] +0x19: seq32[rB] = rA +``` + +Opcode 0x11 stores the three spare words and the per-page result into ARM +memory. Opcodes 0x18/0x19 indirectly access sequencer-local registers/RAM. +The program uses 0x19 to build the unique chip-enable list at `SEQ+0xd60`, +then 0x18 to walk it. A later 0x18 loads the next chip enable through the +pointer at `SEQ+0xd50`. The disassembler and emulator now implement all +three operations. **[decoded]** + +## Program 0x22009030 + +The program first scans the supplied bank list, converts each bank through +the chip-enable table, and records each distinct chip enable in sequencer +memory at `SEQ+0xd60`. It then sends NAND READ (0x00), the row address and +READ START (0x30) for the first pending page on every distinct chip enable +before waiting for any one chip's ready status. This overlaps NAND page-load +time across banks. **[decoded]** + +For each page it selects the corresponding chip enable, waits for status +ready with command 0x70, returns to data output with command 0x00, and runs +four 512-byte data/ECC pipelines. Each chunk stages controller data, starts +the ECC transfer, records an uncorrectable indication as result bit 30 or a +corrected-bit-count indication in the result's low-byte bitmap, and DMA-writes +the corrected 512 bytes to its supplied address. **[decoded]** + +After the chunks it decodes and stores `FMSYND5..7` as the 12-byte spare, +stores the page result word, and ORs that word into the aggregate result. +When another page remains on that chip enable, it starts that NAND page load +before proceeding, maintaining one outstanding load per bank while pages on +other banks are transferred. Finally it clears the DMA/chip-select bits and +ends; the sequencer exposes the aggregate result at `SEQ+0xc30`. **[decoded]** + +The emulator now completes one-page and multi-bank runs. Its clean model +produces result word zero; setting `FMUNK810`'s corrected count field to 3 +produces low-byte result bit 2 (`0x4`) for each page, matching the classifier's +reported correction count of 3. These are emulator observations, not device +measurements. **[measured]** + +## Porting consequences + +- One Rockbox `nand_read_pages` call should accept an ordered physical-page, + bank, data-buffer and spare/result list and reproduce 0x22009030. A + single-page wrapper may use it only after register equivalence and hardware + validation. **[inferred]** +- FTL host reads should split only at logical-block boundaries. A block + portion without a log uses the multiple-VB path; one with a log builds a + scattered physical list. **[decoded]** +- Merge reads should retain Apple's 128-page grouping and page-by-page retry, + including the destination spare's 0x55 ECC mark after a repeated failure. + Any on-flash behavior will be called out before device testing. **[decoded]** +- The existing BootROM-derived `nand_read_page` remains the hardware-proven + fallback until the sequencer implementation has passed on the device. + **[inferred]** diff --git a/utils/ipodnano3g/decode/APPLE-FTL-WRITE.md b/utils/ipodnano3g/decode/APPLE-FTL-WRITE.md new file mode 100644 index 0000000000..7e9da9e3de --- /dev/null +++ b/utils/ipodnano3g/decode/APPLE-FTL-WRITE.md @@ -0,0 +1,84 @@ +# Apple Nano 3G FTL write path (osos 1.1.3, Whimory 2.1), decoded 2026-09-13 + +Disassembly in this directory (`*.s`, from `dis/fn.py`). Whimory source path +string: `Whimory2_1\core\FTL\FTLInter...`. Function names come from the +firmware's own log strings. + +| function | address | Nano 2G equivalent (ftl-nano2g.c) | +|---|---|---| +| `FTL_Write` | 0x0803af64 | `ftl_write()` | +| `_PrepareLog` | 0x0806b5ec | `ftl_allocate_log_entry()` | +| `_FindLogForLbn` | 0x08084998 | `ftl_get_log_entry()` | +| `_Merge` | 0x080dd8c4 | `ftl_remove_scattered_block()` | +| `_DoMoveMerge` | 0x0806f788 | `ftl_compact_scattered()` | +| `_DoCopyMerge` | 0x0806f488 | `ftl_commit_sequential()` | +| `_DoSimpleMerge` | 0x0807c1b0 | `ftl_commit_scattered()` | +| `_FTLMarkCxtNotValid` | 0x080a1e50 | clean_flag block in `ftl_write()` | +| `_GetFreeVb` / `_SetFreeVb` | 0x08067098 / 0x0806724c | `ftl_allocate_pool_block()` / `ftl_release_pool_block()` | +| `_AutoWearLevel` | 0x0807bdfc | `ftl_swap_blocks()` | +| `FTL_Flush` | 0x0803a404 | `ftl_sync()` | +| `_StoreFTLCxt` | 0x0806fc1c | `ftl_commit_cxt()` | +| `_FTLRestore` | 0x08069a4c | (none - nano2g refuses an unclean mount) | + +Globals: 0x08b5ed00 device info (+0x20 u16 pages per superblock, +0x2e u16 +user logical blocks, +0x30 u32 capacity in pages, +0x34 u16 page size); +0x08a34b7c pointers (+4 FTL context, +12 spare buffer array). +FTL context: +4 user usn, +8 freecount, +12 swapcounter, +0x198 map table +pointer, +0x1a4 17 log structs of 20 bytes, +0x318 ctrlpage, +0x31c clean +flag, +0x3c8 refresh-list count. +Log struct: +0 usn (recency), +4 vbn (0xffff none), +6 lbn, +8 offsets +table pointer, +12 pages used, +14 pages current, +16 is-sequential. + +## FTL_Write(lpn, count, buf) +- `_FTLMarkCxtNotValid()` at every call (writes the 0x4f mark only if clean). +- Per logical block: `log = _PrepareLog(lbn)`. + - Log full (pages used == ppb): `_Merge(log)`; if that freed the log's + block, `_GetFreeVb` a new one. + - Whole-block write (count >= ppb, offset 0): into the log block if it is + empty, else into a fresh `_GetFreeVb` block; then `_SetFreeVb` the log + block and the old data block, map[lbn] = new block, log vbn = 0xffff. + - Else append min(ppb - offset, count, ppb - used) pages to the log with + `VFL_WriteMultiplePagesInVb` (0x0805f8f0), updating offsets; sequential + stays set only while every page lands at its own index. + - Last page of a block gets type 0x41 when whole-block or still sequential. + - A full, sequential log is merged immediately (becomes the data block). +- **usn (context +4) increments only when a write starts at page 0 of a + block**; all pages appended to a log share that usn. (Matches the dump: + Apple's logs of 8 and 4 pages each carry one usn.) +- Program failure: log marked full and non-sequential (forces a merge), or a + fresh whole-block target is `_SetFreeVb`'d; then retry. No panic. +- **No commit.** Afterwards: refresh list > 2 entries -> handle refresh; + else swapcounter >= 300 -> swapcounter -= 20 and up to 4 `_AutoWearLevel` + (identical to nano2g). + +## _PrepareLog(lbn) (= nano2g ftl_allocate_log_entry) +- Existing log for lbn: log usn = context usn - 1 (recency), return it. +- Else reuse a slot that has a block but no pages. +- Else if freecount == 3: `_Merge(NULL)`. +- Then take a free slot, `_GetFreeVb` its block. + +## _Merge(log) (= nano2g ftl_remove_scattered_block) +- `_FTLMarkCxtNotValid()`. +- log NULL: victim = lowest usn, ties -> more current pages; sequential -> + `_DoCopyMerge`, else `_DoSimpleMerge`. +- log given: current pages <= ppb/2 -> **`_DoMoveMerge` (compaction)**; + else sequential -> `_DoCopyMerge`, else `_DoSimpleMerge`. +- swapcounter++ per merge. + +## FTL_Flush(mode) +- Clean flag set: return. +- **mode 1: fold every log** (sequential -> `_DoCopyMerge`, else + `_DoSimpleMerge`, swapcounter++ each), then store. +- **other modes: store without folding - logs stay open, recorded in the + context and the 17 0x45 offset-table pages.** The measured dump's commit + (18 free, 2 logs, non-FF 0x45 data) is one of these. +- `_StoreFTLCxt` up to 5 tries, moving ctrlpage to a block end on failure. +- Callers: only the AND call dispatcher (0x080279e4, mode from the OS) and + the end of `_FTLRestore`. Never from the write path. + +## _FTLRestore +Closed blocks (last page 0x41) -> map, highest usn wins; unclosed with page 0 +data -> logs; one log per lbn (loser of an offset-table comparison dropped); +more than 17 logs fails (then FTL_Open fails, "erasing block 0" = reformat); +holes filled with erased blocks; free pool = the rest, must total 20 with +logs; whole pool erased; then FTL_Flush. diff --git a/utils/ipodnano3g/ftltest/.gitignore b/utils/ipodnano3g/ftltest/.gitignore new file mode 100644 index 0000000000..53e65b2e12 --- /dev/null +++ b/utils/ipodnano3g/ftltest/.gitignore @@ -0,0 +1,14 @@ +bench_write +ftl_built.c +replay +test_badblock +test_crash +test_eligible +test_ftl +test_mock +test_restore +test_wear +test_write +test_geometry +test_format +test_permute diff --git a/utils/ipodnano3g/ftltest/Makefile b/utils/ipodnano3g/ftltest/Makefile new file mode 100644 index 0000000000..4b655784ef --- /dev/null +++ b/utils/ipodnano3g/ftltest/Makefile @@ -0,0 +1,109 @@ +R ?= ../../.. +T = $(R)/firmware/target/arm/s5l8702/ipodnano3g +CFLAGS = -O2 -g -std=gnu99 -Wall -Wextra -Wno-unused-parameter -Istubs -I$(T) \ + -include ftl_hooks.h +DUMP ?= + +# The test hooks are appended to the FTL source, each group only if that +# tree does not already define it. +HOOKFLAGS = $(shell grep -q 'int ftl_sb_locate' $(FTL) || echo -DNEED_HOOKS_LOCATE) \ + $(shell grep -q 'uint32_t ftl_pool_snapshot' $(FTL) || echo -DNEED_HOOKS_WEAR) + +FTL = $(T)/ftl-nano3g.c +HDRS = $(T)/ftl-target.h $(T)/nand-target.h mock_nand.h ftl_hooks.h ftl_hooks.c + +ftl_built.c: $(FTL) ftl_hooks.c FORCE + cat $(FTL) ftl_hooks.c > $@ +FORCE: + +all: test_ftl test_mock test_write test_wear test_badblock test_eligible test_restore \ + test_geometry test_format test_permute bench_write + +test_ftl: ftl_built.c mock_nand.c test_ftl.c $(HDRS) + $(CC) $(CFLAGS) $(HOOKFLAGS) -o $@ ftl_built.c mock_nand.c test_ftl.c + +test_mock: mock_nand.c test_mock.c $(HDRS) + $(CC) $(CFLAGS) -o $@ mock_nand.c test_mock.c + +test_write: ftl_built.c mock_nand.c test_write.c $(HDRS) + $(CC) $(CFLAGS) $(HOOKFLAGS) -o $@ ftl_built.c mock_nand.c test_write.c + +test_wear: ftl_built.c mock_nand.c test_wear.c $(HDRS) + $(CC) $(CFLAGS) $(HOOKFLAGS) -o $@ ftl_built.c mock_nand.c test_wear.c + +test_badblock: ftl_built.c mock_nand.c test_badblock.c $(HDRS) + $(CC) $(CFLAGS) $(HOOKFLAGS) -o $@ ftl_built.c mock_nand.c test_badblock.c + +test_eligible: ftl_built.c mock_nand.c test_eligible.c $(HDRS) + $(CC) $(CFLAGS) $(HOOKFLAGS) -o $@ ftl_built.c mock_nand.c test_eligible.c + +test_restore: ftl_built.c mock_nand.c test_restore.c $(HDRS) + $(CC) $(CFLAGS) $(HOOKFLAGS) -o $@ ftl_built.c mock_nand.c test_restore.c + +test_permute: mock_nand.c test_permute.c $(HDRS) + $(CC) $(CFLAGS) -o $@ mock_nand.c test_permute.c + +test_geometry: ftl_built.c mock_nand.c test_geometry.c $(HDRS) + $(CC) $(CFLAGS) $(HOOKFLAGS) -o $@ ftl_built.c mock_nand.c test_geometry.c + +test_format: ftl_built.c mock_nand.c test_format.c $(HDRS) + $(CC) $(CFLAGS) $(HOOKFLAGS) -o $@ ftl_built.c mock_nand.c test_format.c + +replay: ftl_built.c mock_nand.c replay.c $(HDRS) + $(CC) $(CFLAGS) $(HOOKFLAGS) -o $@ ftl_built.c mock_nand.c replay.c + +test_crash: ftl_built.c mock_nand.c test_crash.c $(HDRS) + $(CC) $(CFLAGS) $(HOOKFLAGS) -o $@ ftl_built.c mock_nand.c test_crash.c + +bench_write: ftl_built.c mock_nand.c bench_write.c $(HDRS) + $(CC) $(CFLAGS) $(HOOKFLAGS) -o $@ ftl_built.c mock_nand.c bench_write.c + +check: all + test -n "$(DUMP)" + ./test_mock $(DUMP) + ./test_ftl $(DUMP) + ./test_permute $(DUMP) + ./test_ftl halves:$(DUMP) + ./test_ftl both:$(DUMP) + ./test_write $(DUMP) + ./test_wear $(DUMP) + ./test_badblock $(DUMP) + ./test_eligible $(DUMP) + ./test_restore $(DUMP) + ./test_geometry $(DUMP) + ./test_format blank:2k4 + ./test_format blank:2k8g + ./test_format blank:4k2 + ./test_format blank:halves + ./test_format blank:both + ./test_format blank:single4k + ./test_format blank:split13 + +# Power-cut campaigns on formatted blank media, 4KiB pages included +check-crash-blank: test_crash + for g in 2k4 4k2 halves both single4k split13; do for s in 1 2; do \ + ./test_crash blank:$$g 300 $$s 6000 0 || exit 1; \ + ./test_crash blank:$$g 300 $$s 6000 1 || exit 1; \ + done; done + +# Power-cut campaigns on the dump laid out as modes 9 and 4 +check-crash-layouts: test_crash + test -n "$(DUMP)" + for g in halves both; do for s in 1 2 3 4; do \ + ./test_crash $$g:$(DUMP) 300 $$s 6000 0 || exit 1; \ + ./test_crash $$g:$(DUMP) 300 $$s 6000 1 || exit 1; \ + done; done + +check-crash: test_crash + test -n "$(DUMP)" + for s in $$(seq 1 24); do \ + ./test_crash $(DUMP) 300 $$s 6000 0; \ + ./test_crash $(DUMP) 300 $$s 6000 1; \ + done + +clean: + rm -f ftl_built.c test_ftl test_mock test_write test_wear test_badblock \ + test_eligible test_restore test_geometry test_format test_permute \ + test_crash replay bench_write + +.PHONY: all check check-crash check-crash-blank check-crash-layouts clean FORCE diff --git a/utils/ipodnano3g/ftltest/bench_write.c b/utils/ipodnano3g/ftltest/bench_write.c new file mode 100644 index 0000000000..f444904cce --- /dev/null +++ b/utils/ipodnano3g/ftltest/bench_write.c @@ -0,0 +1,126 @@ +/* What one flush costs, in page programs and block erases, for a few write + * shapes. Amplification is measured against the bytes the caller asked to + * write. */ +#include +#include +#include +#include "nand-target.h" +#include "ftl-target.h" +#include "mock_nand.h" + +#define SBPAGES 1024 + +static uint8_t pat[NAND_PAGE_SIZE]; +static uint32_t *lpns; +static uint32_t nlpn; + +static const void *src(uint32_t lpn, void *ctx) +{ + uint32_t i; + + (void)ctx; + for (i = 0; i < nlpn; i++) + if (lpns[i] == lpn) + return pat; + return NULL; +} + +#define ROUNDS 10 + +static void bench(const char *what, uint32_t n, uint32_t stride, uint32_t base) +{ + uint32_t i, r; + double amp; + + nlpn = n; + for (i = 0; i < n; i++) + lpns[i] = base + i * stride; + mock_reset_stats(); + for (r = 0; r < ROUNDS; r++) + if (ftl_hook_write_merge(lpns[0], lpns[n - 1], src, NULL) || ftl_sync()) + { + printf(" %-38s FAILED\n", what); + return; + } + amp = (double)mock_writes / ROUNDS / n; + printf(" %-38s %7.0f pages, %5.1f block erases, %6.0fx\n", what, + (double)mock_writes / ROUNDS, (double)mock_erases / ROUNDS, amp); +} + +int main(int argc, char **argv) +{ + uint32_t i; + + mock_open(argc > 1 ? argv[1] : "../n3g-dump"); + if (ftl_init()) { puts("mount failed"); return 1; } + lpns = malloc(4096 * sizeof(uint32_t)); + for (i = 0; i < NAND_PAGE_SIZE; i++) + pat[i] = (uint8_t)i; + /* Settle first. A freshly mounted unit has an erase-count spread of + * about 110, so static wear levelling relocates one cold block per + * commit until the spread is under the threshold; that is a real cost + * but a transient one, and it would otherwise be charged to every row + * below. Run until it stops firing. */ + { + uint16_t psb[20], pec[20]; + uint32_t r, n, i, mn, mx; + + ftl_sync(); + ftl_init(); + for (r = 0; r < 200; r++) + { + n = ftl_pool_snapshot(psb, pec, 20); + mn = 0xffff; mx = 0; + for (i = 0; i < n; i++) + { + if (pec[i] < mn) mn = pec[i]; + if (pec[i] > mx) mx = pec[i]; + } + if (n && mx - mn <= 64) + break; + lpns[0] = 900 * SBPAGES + 1; + nlpn = 1; + if (ftl_hook_write_merge(lpns[0], lpns[0], src, NULL) || ftl_sync()) + break; + } + printf("settled after %u levelling rounds " + "(pool erase counts %u..%u)\n\n", r, mn, mx); + ftl_init(); + } + + printf("cost of one flush, averaged over %d, including the 25-page\n" + "commit and any wear-levelling relocation it triggers\n", ROUNDS); + printf(" %-38s %7s %5s %6s\n", "", "written", "erased", "amp"); + bench("1 sector", 1, 1, 100 * SBPAGES + 7); + bench("8 sectors, contiguous", 8, 1, 110 * SBPAGES + 7); + bench("512 sectors, contiguous (1 MiB)", 512, 1, 120 * SBPAGES); + bench("1024 sectors = one whole block (2 MiB)", 1024, 1, 130 * SBPAGES); + bench("2 sectors, 2 blocks apart", 2, SBPAGES, 140 * SBPAGES + 3); + bench("8 sectors, 1 per block", 8, SBPAGES, 150 * SBPAGES + 3); + /* What the storage layer actually does: absorb writes in the overlay, + * push them into log blocks when it fills, and commit only at shutdown. */ + { + uint32_t r, blocks[8] = { 60, 61, 75, 90, 91, 120, 121, 140 }; + + mock_reset_stats(); + for (r = 0; r < 40; r++) + { + nlpn = 1; + lpns[0] = blocks[r % 8] * SBPAGES + 3 + r; + if (ftl_hook_write_merge(lpns[0], lpns[0], src, NULL)) + break; + } + printf("\n 40 scattered 1-sector writes, no sync yet:\n" + " %lu pages written, %lu block erases\n", + mock_writes, mock_erases); + ftl_sync(); + printf(" then one sync (folds the logs):\n" + " %lu pages written, %lu block erases in total, %.0fx\n", + mock_writes, mock_erases, (double)mock_writes / 40); + } + + printf("\none superblock = %d pages = %d KiB; erases are physical blocks,\n", + SBPAGES, SBPAGES * NAND_PAGE_SIZE / 1024); + printf("8 per superblock (4 banks x 2 planes)\n"); + return 0; +} diff --git a/utils/ipodnano3g/ftltest/ftl_hooks.c b/utils/ipodnano3g/ftltest/ftl_hooks.c new file mode 100644 index 0000000000..44ead7a21e --- /dev/null +++ b/utils/ipodnano3g/ftltest/ftl_hooks.c @@ -0,0 +1,470 @@ +/* Query hooks for the host tests. They read the FTL's static state, so this + * file is appended to ftl-nano3g.c at build time (see the Makefile) rather + * than compiled on its own. A hook group is only appended when the FTL + * source lacks it, so this also + * builds against trees that still define them. */ + +#include "mock_nand.h" + +#ifdef NEED_HOOKS_LOCATE +/* The remap slot whose reserved block is physical block block: what it + * replaces, or 0xfff0/0xffff; -1 if block is not a reserved block */ +int ftl_vfl_pool_entry(uint32_t bank, uint32_t block) +{ + uint32_t k; + + if (!geo || bank >= geo->banks) + return -1; + for (k = 0; k < geo->planes * geo->vflspares; k++) + if (ftl_vfl_spare_block(k) == block) + return ftl_vfl_cxt[bank].remap[k]; + return -1; +} + +int ftl_sb_locate(uint32_t sb, uint32_t v, uint32_t *bank, uint32_t *page) +{ + if (!ftl_mounted || sb >= nsuperblocks || v >= sbpages) + return -1; + ftl_vpage_phys(sb, v, bank, page); + return 0; +} + +int ftl_locate(uint32_t lpn, uint32_t *bank, uint32_t *page) +{ + uint32_t sb, v; + + if (!ftl_mounted || ftl_resolve(lpn, &sb, &v)) + return -1; + ftl_vpage_phys(sb, v, bank, page); + return 0; +} +#endif + +#if defined(NEED_HOOKS_WEAR) && defined(FTL_COMMIT_PAGES) /* needs the write path */ +void ftl_global_spread(uint32_t *lowest, uint32_t *highest) +{ + uint32_t i, mn = 0xffff, mx = 0; + + for (i = 0; i < nsuperblocks; i++) + { + if (ftl_erasectr[i] == 0xffff) + continue; + if (ftl_erasectr[i] < mn) mn = ftl_erasectr[i]; + if (ftl_erasectr[i] > mx) mx = ftl_erasectr[i]; + } + *lowest = mn; + *highest = mx; +} + +uint32_t ftl_pool_snapshot(uint16_t *sb, uint16_t *erasectr, uint32_t max) +{ + uint32_t i, n = 0; + + if (!ftl_mounted) + return 0; + for (i = 0; i < ftl_cxt.freecount && n < max; i++) + { + sb[n] = ftl_cxt.blockpool[(ftl_cxt.nextfreeidx + i) % FTL_POOL_SIZE]; + erasectr[n] = sb[n] < nsuperblocks ? ftl_erasectr[sb[n]] : 0xffff; + n++; + } + return n; +} +#endif + +/* Pool state behind ftl_pool_alloc()'s choice: blocks free at the last + * commit are safe to hand out, blocks released since are not */ +void ftl_pool_state(uint32_t *freecount, uint32_t *fresh, uint32_t *nlogs) +{ + *freecount = ftl_cxt.freecount; + *fresh = 0; /* no fresh-block rule any more */ + *nlogs = ftl_nlogs; +} + +/* ftl_write_merge() and the write state are private to the FTL; the tests + * reach them through these */ +int ftl_hook_write_merge(uint32_t first, uint32_t last, + const void *(*src)(uint32_t lpn, void *ctx), + void *ctx) +{ + return ftl_write_merge(first, last, src, ctx); +} + +bool ftl_hook_writeable(void) +{ + return ftl_mounted && ftl_writable; +} + +/* The three FTL control superblocks, so a test can roll them back */ +void ftl_hook_ctrl(uint16_t *sb) +{ + unsigned int i; + + for (i = 0; i < 3; i++) + sb[i] = ftl_ctrl[i]; +} + +/* Consistency of the mounted FTL's RAM state against itself and the medium. + * Returns 0, or writes a description of the first problem into msg. */ +int ftl_hook_check(char *msg, size_t len) +{ + static uint8_t use[FTL_MAX_SB]; /* 1 map, 2 pool, 3 log, 4 ctrl */ + uint32_t i, v, sb; + + memset(use, 0, sizeof(use)); + for (i = 0; i < 3; i++) + if (ftl_ctrl[i] < nsuperblocks) + use[ftl_ctrl[i]] = 4; + for (i = 0; i < usersb; i++) + { + sb = ftl_map[i]; + if (use[sb]) + { + snprintf(msg, len, "map lblock %u -> sb %u, already used as %u", i, sb, use[sb]); + return -1; + } + use[sb] = 1; + } + for (i = 0; i < ftl_nlogs; i++) + { + const struct ftl_log *log = &ftl_logs[i]; + sb = log->sb; + if (use[sb]) + { + snprintf(msg, len, "log %u (lblock %u) sb %u already used as %u", i, log->lblock, sb, use[sb]); + return -1; + } + use[sb] = 3; + for (v = 0; v < sbpages; v++) + if (log->offsets[v] != FTL_NO_PAGE) + { + if (ftl_read_vpage(sb, log->offsets[v]) < 0 || !meta_is_data(ftl_meta) + || ftl_meta[0] != log->lblock * sbpages + v) + { + snprintf(msg, len, "log lblock %u idx %u -> sb %u v %u holds lpn %x type %x", + log->lblock, v, sb, log->offsets[v], ftl_meta[0], meta_type(ftl_meta)); + return -1; + } + } + } + if (1) + for (i = 0; i < ftl_cxt.freecount; i++) + { + sb = ftl_cxt.blockpool[(ftl_cxt.nextfreeidx + i) % FTL_POOL_SIZE]; + if (use[sb]) + { + snprintf(msg, len, "pool sb %u already used as %u", sb, use[sb]); + return -1; + } + use[sb] = 2; + } + if (ftl_cxt.freecount + ftl_nlogs > FTL_POOL_SIZE) + { + snprintf(msg, len, "free %u + logs %u > 20", ftl_cxt.freecount, ftl_nlogs); + return -1; + } + if (!ftl_writable) + { + snprintf(msg, len, "not writable after mount"); + return -1; + } + return 0; +} + +/* What ftl_load_cxt() sees in the three control blocks */ +void ftl_hook_ctrl_summary(void) +{ + uint32_t i, v, last; + int type, lt; + + for (i = 0; i < 3; i++) + { + uint32_t u0 = 0; + int t0 = ftl_read_vpage(ftl_ctrl[i], 0); + u0 = ftl_meta[0]; + last = 0; lt = -9; + for (v = 0; v < sbpages; v++) + { + type = ftl_read_vpage(ftl_ctrl[i], v); + if (type == SPARE_ERASED) break; + lt = type; last = v; + } + printf(" ctrl %u = sb %u: page0 type %x usn %08x, used %u, last type %x usn %08x\n", + i, ftl_ctrl[i], t0, u0, v, lt, ftl_meta[0]); + (void)last; + } + printf(" ftl_ctrl_idx %u ftl_ctrl_v %u cxt.usn %08x cleanflag %u maxusn %u\n", + ftl_ctrl_idx, ftl_ctrl_v, ftl_cxt.usn, ftl_cxt.cleanflag, ftl_cxt.maxusn); +} + +/* Fold every open log and commit, leaving the medium's data unchanged: + * sector 0 written back as it is, then a full flush and a remount */ +int ftl_hook_settle(void) +{ + static uint8_t s0[NAND_PAGE_SIZE]; + + if (ftl_read(0, 1, s0) || ftl_write(0, 1, s0) || ftl_sync()) + return -1; + return ftl_init(); +} + +/* Lay down a clean, empty volume on a blank mock medium in Apple's format, + * for geometries no dump exists of: per bank a VFL context whose ring is + * blocks 1-4, with the blocks they and block 0 fall in remapped to the + * first reserved blocks as on the dumped unit; FTL control superblocks 0-2; + * logical block n in superblock 3 + n; the 20 after them free; then one + * commit. Every data page reads erased. mock_open_blank() first. */ +int ftl_hook_format(void) +{ + uint32_t bank, i, n; + + ftl_mounted = false; + mutex_init(&ftl_mtx); + ftl_nholes = 0; + if (ftl_setup_geometry()) + return -1; + ftl_vfl_usn = 0; + for (bank = 0; bank < geo->banks; bank++) + { + struct ftl_vfl_cxt *cxt = &ftl_vfl_cxt[bank]; + uint32_t used[4] = { 0, 0, 0, 0 }; + + memset(cxt, 0, sizeof(*cxt)); + cxt->updatecount = 0xffffffff; + for (i = 0; i < 3; i++) + cxt->ftlctrlblocks[i] = i; + for (i = 0; i < 4; i++) + cxt->vflcxtblocks[i] = 1 + i; + for (i = 0; i < geo->planes * geo->vflspares; i++) + cxt->remap[i] = FTL_VFL_FREE; + /* Physical blocks 0-4, each in the table of the unit it belongs to */ + for (i = 0; i <= 4; i++) + { + uint32_t unit = 0, v = 0; + + while (ftl_unit_block(unit, v) != i) + if (++v == 8) + { + v = 0; + unit++; + } + cxt->remap[unit * geo->vflspares + used[unit]++] = i; + } + for (i = 0; i < 4; i++) + cxt->usedcount[i] = used[i]; + if (ftl_vfl_store_cxt(bank)) + return -2; + } + + memcpy(ftl_ctrl, ftl_vfl_cxt[0].ftlctrlblocks, sizeof(ftl_ctrl)); + for (i = 0; i < usersb; i++) + ftl_map[i] = 3 + i; + memset(&ftl_cxt, 0, sizeof(ftl_cxt)); + memset(ftl_cxt.field_3d8, 0xff, sizeof(ftl_cxt.field_3d8)); + for (n = 0; n < FTL_POOL_SIZE; n++) + ftl_cxt.blockpool[n] = 3 + usersb + n; + ftl_cxt.freecount = FTL_POOL_SIZE; + ftl_cxt.usn = 0xfffffff0; + ftl_nlogs = 0; + memset(ftl_erasectr, 0, sizeof(ftl_erasectr)); + memset(ftl_readcount, 0, sizeof(ftl_readcount)); + memset(ftl_stats, 0xff, sizeof(ftl_stats)); + ftl_nmappages = (usersb * 2 + pagesize - 1) / pagesize; + ftl_necpages = (nsuperblocks * 2 + pagesize - 1) / pagesize; + ftl_nrcpages = ftl_necpages; + ftl_nlogpages = (sbpages * FTL_MAX_LOGS * 2 + pagesize - 1) / pagesize; + ftl_ctrl_idx = 0; + ftl_ctrl_v = 0; + ftl_mounted = true; + ftl_writable = true; + return ftl_commit() ? -3 : 0; +} + +/* A medium from what nandcheck.py collected from a unit: report.txt for the + * geometry, meta.bin for every page's spare record and pages.bin for every + * page that is not user data. User data was not collected and reads as + * zeros, which is enough to mount, resolve every logical page and write. */ +static int open_collected(const char *dir) +{ + char path[512], line[128]; + unsigned banks = 0, blocks = 0, ppb = 0, pagesize = 0, userblocks = 0; + unsigned mode = 0, planes = 0, vflspares = 0; + size_t npages, g; + uint32_t rec[2]; + FILE *f; + + snprintf(path, sizeof(path), "%s/report.txt", dir); + if (!(f = fopen(path, "r"))) + return -1; + while (fgets(line, sizeof(line), f)) + { + sscanf(line, "banks %u", &banks); + sscanf(line, "blocks %u", &blocks); + sscanf(line, "ppb %u", &ppb); + sscanf(line, "pagesize %u", &pagesize); + sscanf(line, "userblocks %u", &userblocks); + sscanf(line, "mode %u", &mode); + sscanf(line, "planes %u", &planes); + sscanf(line, "vflspares %u", &vflspares); + } + fclose(f); + if (!banks || !blocks || !ppb || !pagesize || !planes) + return -1; + mock_geo.banks = banks; + mock_geo.blocks = blocks; + mock_geo.pagesperblock = ppb; + mock_geo.pagesize = pagesize; + mock_geo.userblocks = userblocks; + mock_geo.planes = planes; + mock_geo.vflspares = vflspares; + mock_geo.mode = mode; + mock_geo.twoplane = mode == 8; + mock_geo.layout = mode == 1 ? NAND_LAYOUT_SINGLE + : mode == 3 || mode == 8 ? NAND_LAYOUT_ADJACENT + : mode == 2 || mode == 9 || mode == 12 ? NAND_LAYOUT_HALVES + : mode == 4 ? NAND_LAYOUT_BOTH + : mode == 13 ? NAND_LAYOUT_SPLIT13 : NAND_LAYOUT_UNKNOWN; + mock_geo.validated = true; /* a host copy: writes are safe */ + mock_open_blank(); + npages = (size_t)banks * blocks * ppb; + + snprintf(path, sizeof(path), "%s/meta.bin", dir); + if (!(f = fopen(path, "rb")) || fread(mock_meta, 16, npages, f) != npages) + return -1; + fclose(f); + snprintf(path, sizeof(path), "%s/pages.bin", dir); + if (!(f = fopen(path, "rb"))) + return -1; + while (fread(rec, 4, 2, f) == 2) + { + g = (size_t)rec[0] * blocks * ppb + rec[1]; + if (g >= npages + || fread(mock_data + g * pagesize, 1, pagesize, f) != pagesize) + break; + } + fclose(f); + return 0; +} + +/* Open the medium a test names: a dump directory, or "blank:" and a preset + * geometry, formatted with ftl_hook_format(). The presets are rows of + * Apple's chip table, with the reserved count nand-nano3g.c derives. */ +int ftl_hook_open(const char *spec) +{ + static const struct + { + const char *name; + unsigned banks, blocks, pagesize, userblocks, mode, planes, layout; + } presets[] = + { + /* A514D3AD x 4, 4GB */ + { "2k4", 4, 4096, 2048, 3872, 8, 2, NAND_LAYOUT_ADJACENT }, + /* A555D5AD x 4, 8GB */ + { "2k8g", 4, 8192, 2048, 7744, 8, 2, NAND_LAYOUT_ADJACENT }, + /* B614D5EC x 2, 4GB */ + { "4k2", 2, 4096, 4096, 3872, 8, 2, NAND_LAYOUT_ADJACENT }, + /* 2555D5EC x 4 and A5D5D589 x 4, 8GB */ + { "halves", 4, 8192, 2048, 7744, 9, 2, NAND_LAYOUT_HALVES }, + /* A5D5D589 x 2, 4GB */ + { "both", 2, 8192, 2048, 7744, 4, 4, NAND_LAYOUT_BOTH }, + /* B614D5EC x 4, 8GB */ + { "single4k", 4, 4096, 4096, 3872, 1, 1, NAND_LAYOUT_SINGLE }, + /* A585D598 x 4, 8GB */ + { "split13", 4, 8320, 2048, 7744, 13, 2, NAND_LAYOUT_SPLIT13 }, + }; + unsigned i; + + if (!strncmp(spec, "collected:", 10)) + return open_collected(spec + 10); + if (!strncmp(spec, "halves:", 7)) + { + mock_open_layout(spec + 7, "halves"); + return 0; + } + if (!strncmp(spec, "both:", 5)) + { + mock_open_layout(spec + 5, "both"); + return 0; + } + if (strncmp(spec, "blank:", 6)) + { + mock_open(spec); + return 0; + } + for (i = 0; i < ARRAYLEN(presets); i++) + if (!strcmp(spec + 6, presets[i].name)) + break; + if (i == ARRAYLEN(presets)) + return -1; + mock_geo.banks = presets[i].banks; + mock_geo.blocks = presets[i].blocks; + mock_geo.pagesperblock = 128; + mock_geo.planes = presets[i].planes; + mock_geo.userblocks = presets[i].userblocks; + mock_geo.vflspares = (presets[i].blocks - presets[i].userblocks) + / presets[i].planes - 23; + mock_geo.twoplane = presets[i].mode == 8; + mock_geo.pagesize = presets[i].pagesize; + mock_geo.mode = presets[i].mode; + mock_geo.layout = presets[i].layout; + mock_geo.validated = true; + mock_open_blank(); + return ftl_hook_format(); +} + +/* The unit a physical block belongs to under the mounted layout, spares + * included, for tests that order pages as a superblock does; -1 if none */ +int ftl_hook_block_unit(uint32_t block) +{ + static int16_t unit_of[8320]; + static const struct nand_geometry *built; + uint32_t u, v; + + if (!geo || block >= geo->blocks || geo->blocks > ARRAYLEN(unit_of)) + return -1; + if (built != geo) + { + for (v = 0; v < ARRAYLEN(unit_of); v++) + unit_of[v] = -1; + for (u = 0; u < geo->planes; u++) + for (v = 0; v < geo->blocks / geo->planes; v++) + unit_of[ftl_unit_block(u, v)] = u; + built = geo; + } + return unit_of[block]; +} + +/* A bad spare followed by a replaced block in unit 0's table, as a spare + * that fails its erase and the next remap leave it: mark the first free + * slot bad, then remap vblock onto the following one */ +int ftl_hook_bad_spare_then_remap(uint32_t bank, uint32_t vblock) +{ + struct ftl_vfl_cxt *cxt = &ftl_vfl_cxt[bank]; + uint32_t k; + + for (k = 0; k < geo->vflspares; k++) + if (cxt->remap[k] == FTL_VFL_FREE) + break; + if (k == geo->vflspares) + return -1; + cxt->remap[k] = FTL_VFL_BAD; + cxt->badcount++; + return ftl_vfl_remap_block(bank, 0, vblock); +} + +/* Put the block unit's vblock currently maps to on bank's pending-bad + * list and commit, as Apple's firmware does after a failed read or + * write; then erase it through the VFL, which must replace it first. + * Returns the pending count left and whether the block moved. */ +int ftl_hook_pending_erase(uint32_t bank, uint32_t unit, uint32_t vblock, + bool *moved) +{ + struct ftl_vfl_cxt *cxt = &ftl_vfl_cxt[bank]; + uint32_t before = ftl_vfl_phys_block(bank, unit, vblock); + + cxt->pending[cxt->pendingcount++] = before; + if (ftl_vfl_commit_cxt(bank) || ftl_vfl_erase(bank, unit, vblock)) + return -1; + *moved = ftl_vfl_phys_block(bank, unit, vblock) != before; + return cxt->pendingcount; +} diff --git a/utils/ipodnano3g/ftltest/ftl_hooks.h b/utils/ipodnano3g/ftltest/ftl_hooks.h new file mode 100644 index 0000000000..ba328095a3 --- /dev/null +++ b/utils/ipodnano3g/ftltest/ftl_hooks.h @@ -0,0 +1,26 @@ +/* Declarations for ftl_hooks.c; force-included into every test. */ +#ifndef FTL_HOOKS_H +#define FTL_HOOKS_H +#include +int ftl_vfl_pool_entry(uint32_t bank, uint32_t block); +int ftl_sb_locate(uint32_t sb, uint32_t v, uint32_t *bank, uint32_t *page); +int ftl_locate(uint32_t lpn, uint32_t *bank, uint32_t *page); +void ftl_global_spread(uint32_t *lowest, uint32_t *highest); +uint32_t ftl_pool_snapshot(uint16_t *sb, uint16_t *erasectr, uint32_t max); +void ftl_pool_state(uint32_t *freecount, uint32_t *fresh, uint32_t *nlogs); +int ftl_hook_write_merge(uint32_t first, uint32_t last, + const void *(*src)(uint32_t lpn, void *ctx), + void *ctx); +_Bool ftl_hook_writeable(void); +void ftl_hook_ctrl(uint16_t *sb); +#include +int ftl_hook_check(char *msg, size_t len); +void ftl_hook_ctrl_summary(void); +int ftl_hook_settle(void); +int ftl_hook_format(void); +int ftl_hook_open(const char *spec); +int ftl_hook_block_unit(uint32_t block); +int ftl_hook_bad_spare_then_remap(uint32_t bank, uint32_t vblock); +int ftl_hook_pending_erase(uint32_t bank, uint32_t unit, uint32_t vblock, + _Bool *moved); +#endif diff --git a/utils/ipodnano3g/ftltest/mock_nand.c b/utils/ipodnano3g/ftltest/mock_nand.c new file mode 100644 index 0000000000..cfc2f056ee --- /dev/null +++ b/utils/ipodnano3g/ftltest/mock_nand.c @@ -0,0 +1,793 @@ +/* Host mock of the Nano 3G NAND driver, backed by the raw dumps: + * n3g-dump/nand_data_4banks.bin page data, bank by bank + * n3g-dump/meta_all.bin 16 bytes per page: spare words 0..2 and + * the nand_read_page() result + * + * Both are mapped MAP_PRIVATE, so erase and program are real - they change + * what later reads return - but they are copy-on-write in RAM and never + * touch the files. The 4GB mapping costs only the pages actually written. + * + * The NAND rules modelled here, measured on the device over a whole 4GB + * unit (see ../RESULTS.md): programming only ever clears bits; an erased page + * reads all-0xff with all-0xff metadata and result NAND_ECC_CLEAN; every + * written page reads NAND_ECC_CORRECTED. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "nand-target.h" +#include "mock_nand.h" + +/* The geometry the FTL sees: the dump's, unless mock_open_blank() or + * mock_open_layout() set another */ +static uint32_t BANKS = 4, BLOCKS = 4096, PPB = 128, PAGESIZE = 2048; +#define NPAGES ((size_t)BANKS * BLOCKS * PPB) +#define METASZ 16 + +/* Address translation from the numbering the FTL uses onto the backing + * store. The store is laid out bank-major in SBLOCKS blocks per bank; for + * mock_open() and mock_open_blank() that is the identity, and for + * mock_open_layout() the store is the dump's mode 8 layout while the FTL + * sees another (see mock_layout_to_dump()). */ +#define XL_IDENTITY 0 +#define XL_HALVES 1 +#define XL_BOTH 2 +static int xlayout = XL_IDENTITY; +static uint32_t SBLOCKS = 4096; +#define DUMP_BANKS 4 +#define DUMP_BLOCKS 4096 + +static size_t store_page(uint32_t bank, uint32_t page); + +uint8_t *mock_data; +uint8_t *mock_meta; +unsigned long mock_reads; +unsigned long mock_read_runs, mock_read_run_pages; +unsigned long mock_writes; +unsigned long mock_writes_by_type[256]; +unsigned long mock_crash_after; +jmp_buf mock_crash_jmp; + +void *mock_crash_bt[32]; +int mock_crash_btn; + +int mock_crash_torn; + +static void set_rc(size_t g, int32_t rc); + +/* g: the page (program) or first page of the block (erase), is_erase */ +static void crash_point(size_t g, int is_erase) +{ + if (mock_crash_after && !--mock_crash_after) + { + mock_crash_btn = backtrace(mock_crash_bt, 32); + if (mock_crash_torn && !is_erase) + { + /* A program cut short: garbage that fails ECC */ + memset(mock_data + g * PAGESIZE, 0x5a, PAGESIZE); + memset(mock_meta + g * METASZ, 0x00, NAND_META_WORDS * 4); + set_rc(g, NAND_ECC_FAILED); + } + else if (mock_crash_torn && is_erase) + { + /* An erase cut short: the first half of the block erased, the + * rest unreadable */ + uint32_t p; + for (p = 0; p < PPB; p++) + { + if (p < PPB / 2) + { + memset(mock_data + (g + p) * PAGESIZE, 0xff, PAGESIZE); + memset(mock_meta + (g + p) * METASZ, 0xff, NAND_META_WORDS * 4); + set_rc(g + p, NAND_ECC_CLEAN); + } + else + set_rc(g + p, NAND_ECC_FAILED); + } + } + longjmp(mock_crash_jmp, 1); + } +} +unsigned long mock_erases; +unsigned long mock_reprogram; +unsigned long mock_nonsequential; + +struct nand_geometry mock_geo = { + .banks = 4, .blocks = 4096, .pagesperblock = 128, .planes = 2, + .userblocks = 3872, .vflspares = 89, .twoplane = true, .pagesize = 2048, + .mode = 8, .layout = NAND_LAYOUT_ADJACENT, .validated = true, +}; + +unsigned long mock_runs, mock_run_pages, mock_twoplane_runs, mock_twoplane_pages; +unsigned long mock_bad_twoplane; + +/* Injected bad blocks */ +#define MAX_BAD 32 +static struct { uint32_t bank, block; int how; } bad[MAX_BAD]; +static unsigned int nbad; +unsigned long mock_write_fails, mock_erase_fails; + +void mock_set_bad(uint32_t bank, uint32_t block, int how) +{ + if (nbad < MAX_BAD) + { + bad[nbad].bank = bank; + bad[nbad].block = block; + bad[nbad].how = how; + nbad++; + } +} + +void mock_clear_bad(void) +{ + nbad = 0; + mock_write_fails = mock_erase_fails = 0; +} + +static int is_bad(uint32_t bank, uint32_t block, int how) +{ + unsigned int i; + + for (i = 0; i < nbad; i++) + if (bad[i].bank == bank && bad[i].block == block + && (bad[i].how & how)) + return 1; + return 0; +} + +static uint8_t *map_file(const char *path, size_t want) +{ + int fd = open(path, O_RDONLY); + struct stat st; + void *p; + + if (fd < 0 || fstat(fd, &st) || (size_t)st.st_size != want) + { + fprintf(stderr, "%s: missing or wrong size\n", path); + exit(2); + } + /* MAP_PRIVATE + PROT_WRITE: writes are copy-on-write, the file is + * never modified. MAP_NORESERVE keeps the 4GB mapping from being + * charged against commit limits up front. */ + p = mmap(NULL, want, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_NORESERVE, fd, 0); + if (p == MAP_FAILED) + { + perror("mmap"); + exit(2); + } + close(fd); + return p; +} + +/* A blank medium of any geometry: every page erased. Anonymous zero pages + * read as erased (see nand_read_page), so only written pages cost memory. */ +void mock_open_blank(void) +{ + BANKS = mock_geo.banks; + BLOCKS = mock_geo.blocks; + PPB = mock_geo.pagesperblock; + PAGESIZE = mock_geo.pagesize; + xlayout = XL_IDENTITY; + SBLOCKS = BLOCKS; + mock_data = mmap(NULL, NPAGES * PAGESIZE, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0); + mock_meta = mmap(NULL, NPAGES * METASZ, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0); + if (mock_data == MAP_FAILED || mock_meta == MAP_FAILED) + { + perror("mmap"); + exit(2); + } + mock_reset_stats(); +} + +void mock_open(const char *dir) +{ + char path[512]; + + xlayout = XL_IDENTITY; + SBLOCKS = BLOCKS; + + snprintf(path, sizeof(path), "%s/nand_data_4banks.bin", dir); + mock_data = map_file(path, NPAGES * PAGESIZE); + snprintf(path, sizeof(path), "%s/meta_all.bin", dir); + mock_meta = map_file(path, NPAGES * METASZ); + mock_reset_stats(); +} + +void mock_reset_stats(void) +{ + mock_reads = mock_writes = mock_erases = 0; + mock_read_runs = mock_read_run_pages = 0; + mock_reprogram = mock_nonsequential = 0; + mock_runs = mock_run_pages = mock_twoplane_runs = mock_twoplane_pages = 0; + mock_bad_twoplane = 0; + memset(mock_writes_by_type, 0, sizeof(mock_writes_by_type)); +} + +static int32_t page_rc(size_t g) +{ + int32_t rc; + memcpy(&rc, mock_meta + g * METASZ + 12, 4); + return rc; +} + +static void set_rc(size_t g, int32_t rc) +{ + memcpy(mock_meta + g * METASZ + 12, &rc, 4); +} + +/* An erased page is the one that reads NAND_ECC_CLEAN; every written page + * reads NAND_ECC_CORRECTED. */ +static int is_erased(size_t g) +{ + return page_rc(g) == NAND_ECC_CLEAN; +} + +int nand_read_page(uint32_t bank, uint32_t page, void *databuf, + uint32_t *meta) +{ + size_t g; + + if (bank >= BANKS || page >= BLOCKS * PPB) + return -1; + g = store_page(bank, page); + mock_reads++; + /* An erased page reads all 0xff, as every one in the dump does; a blank + * medium's untouched pages are zero-filled and read the same way */ + if (is_erased(g)) + { + memset(databuf, 0xff, PAGESIZE); + if (meta) + memset(meta, 0xff, NAND_META_WORDS * 4); + return NAND_ECC_CLEAN; + } + memcpy(databuf, mock_data + g * PAGESIZE, PAGESIZE); + if (meta) + memcpy(meta, mock_meta + g * METASZ, NAND_META_WORDS * 4); + return page_rc(g); +} + +int nand_read_pages(struct nand_read *r, unsigned int n) +{ + unsigned int i; + + if (!n) + return -1; + mock_read_runs++; + mock_read_run_pages += n; + for (i = 0; i < n; i++) + { + r[i].ecc = nand_read_page(r[i].bank, r[i].page, r[i].buf, r[i].meta); + if (r[i].ecc < 0) + return -1; + } + return 0; +} + +int nand_write_page(uint32_t bank, uint32_t page, const void *databuf, + const uint32_t *meta) +{ + size_t g; + const uint8_t *src = databuf; + uint8_t *dst; + uint32_t i; + + if (bank >= BANKS || page >= BLOCKS * PPB) + return -1; + g = store_page(bank, page); + crash_point(g, 0); + if (is_bad(bank, page / PPB, MOCK_FAIL_WRITE)) + { + mock_write_fails++; + return NAND_OP_FAILED; + } + mock_writes++; + mock_writes_by_type[(meta[2] >> 8) & 0xff]++; + + /* Programming a page that is not erased is undefined on real NAND; + * the FTL must never do it. Count it, then model what the chip does. */ + if (!is_erased(g)) + mock_reprogram++; + /* Pages within a block must be programmed in order on MLC. */ + if ((page % PPB) && is_erased(g - 1)) + mock_nonsequential++; + + /* Program only ever clears bits, from all 0xff if erased */ + if (is_erased(g)) + { + memset(mock_data + g * PAGESIZE, 0xff, PAGESIZE); + memset(mock_meta + g * METASZ, 0xff, NAND_META_WORDS * 4); + } + dst = mock_data + g * PAGESIZE; + for (i = 0; i < PAGESIZE; i++) + dst[i] &= src[i]; + dst = mock_meta + g * METASZ; + for (i = 0; i < NAND_META_WORDS * 4; i++) + dst[i] &= ((const uint8_t *)meta)[i]; + set_rc(g, NAND_ECC_CORRECTED); + return 0; +} + +/* A run is its pages one after another, as far as the medium can tell. A + * failing page stops the run there, which is one of the outcomes the driver + * allows ("any page of the run may not have been written"). Two-plane runs + * are checked for pairs the chip could actually take: plane 0 then plane 1 + * of the same bank, the same page, in a block and its twin. */ +int nand_write_pages(const struct nand_write *w, unsigned int n, + bool two_plane, uint32_t *failbank) +{ + unsigned int i; + int rc; + + if (!n || (two_plane && (n & 1))) + return -1; + mock_runs++; + mock_run_pages += n; + if (two_plane) + { + mock_twoplane_runs++; + mock_twoplane_pages += n; + for (i = 0; i < n; i += 2) + if (w[i].bank != w[i + 1].bank + || (w[i].page / PPB) % 2 != 0 + || w[i + 1].page != w[i].page + PPB) + mock_bad_twoplane++; + } + for (i = 0; i < n; i++) + { + rc = nand_write_page(w[i].bank, w[i].page, w[i].buf, w[i].meta); + if (rc) + { + if (failbank) + *failbank = w[i].bank; + return rc; + } + } + return 0; +} + +int nand_erase_block(uint32_t bank, uint32_t block) +{ + size_t g; + uint32_t p; + + if (bank >= BANKS || block >= BLOCKS) + return -1; + g = store_page(bank, block * PPB); + crash_point(g, 1); + if (is_bad(bank, block, MOCK_FAIL_ERASE)) + { + mock_erase_fails++; + return NAND_OP_FAILED; + } + mock_erases++; + memset(mock_data + g * PAGESIZE, 0xff, (size_t)PPB * PAGESIZE); + for (p = 0; p < PPB; p++) + { + memset(mock_meta + (g + p) * METASZ, 0xff, NAND_META_WORDS * 4); + set_rc(g + p, NAND_ECC_CLEAN); + } + return 0; +} + +int mock_snapshot(const char *dir) +{ + char path[512]; + int fd; + + snprintf(path, sizeof(path), "%s/nand_data_4banks.bin", dir); + fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd < 0) + return -1; + if (write(fd, mock_data, NPAGES * PAGESIZE) + != (ssize_t)(NPAGES * PAGESIZE)) + { + close(fd); + return -1; + } + close(fd); + + snprintf(path, sizeof(path), "%s/meta_all.bin", dir); + fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd < 0) + return -1; + if (write(fd, mock_meta, NPAGES * METASZ) != (ssize_t)(NPAGES * METASZ)) + { + close(fd); + return -1; + } + close(fd); + return 0; +} + +unsigned int nand_get_bank_count(void) +{ + return BANKS; +} + +const struct nand_geometry *nand_get_geometry(void) +{ + return &mock_geo; +} + +/* ---- Other layouts over the dump ---- + * + * The dump's chip is mode 8: VFL block v of unit u is physical block 2v+u, + * four banks of 4096 blocks. mock_open_layout() presents the same pages as a + * chip of another layout would hold them - each superblock's pages in the + * same order - by translating block numbers on every access. Only the VFL + * contexts must change, because they hold physical block numbers; they are + * rewritten in the copy-on-write mapping when the layout is opened. */ + +/* Apple formats its context ring within physical blocks 1..199, but the + * layout formulas put the dump's context block (block 1 of each bank) at + * 2048 or 4096. So each new bank swaps two blocks of the formula's + * numbering: new block 1 becomes the primary dump bank's block 1, and the + * block the formula put at 1 (halves: the same bank's block 2; both: the + * secondary bank's block 0) takes block 1's formula place. Dump blocks 0-4 + * are all remapped by the VFL, so no FTL data moves. */ +static uint32_t swap_cxt_block(uint32_t block) +{ + uint32_t other = xlayout == XL_HALVES ? DUMP_BLOCKS / 2 : DUMP_BLOCKS; + + if (xlayout == XL_IDENTITY) + return block; + return block == 1 ? other : block == other ? 1 : block; +} + +void mock_layout_to_dump(uint32_t bank, uint32_t block, + uint32_t *dbank, uint32_t *dblock) +{ + uint32_t u, v, hi, r; + + block = swap_cxt_block(block); + switch (xlayout) + { + case XL_HALVES: + u = block >= DUMP_BLOCKS / 2; + v = block - u * (DUMP_BLOCKS / 2); + *dbank = bank; + *dblock = 2 * v + u; + break; + case XL_BOTH: + hi = block >= DUMP_BLOCKS; + r = block - hi * DUMP_BLOCKS; + v = r / 2; + u = (r & 1) + 2 * hi; + *dbank = 2 * (u % 2) + bank; + *dblock = 2 * v + u / 2; + break; + default: + *dbank = bank; + *dblock = block; + break; + } +} + +/* The layout formula alone, without the context block swap: where the FTL + * computes a block to be. A VFL remap entry names a replaced block this way, + * whatever physically holds its (never read) contents. */ +static void formula_from_dump(uint32_t dbank, uint32_t dblock, + uint32_t *bank, uint32_t *block) +{ + uint32_t u, v = dblock / 2, plane = dblock % 2; + + switch (xlayout) + { + case XL_HALVES: + *bank = dbank; + *block = v + plane * (DUMP_BLOCKS / 2); + break; + case XL_BOTH: + u = 2 * plane + dbank / 2; + *bank = dbank % 2; + *block = 2 * v + (u & 1) + ((u & 2) ? DUMP_BLOCKS : 0); + break; + default: + *bank = dbank; + *block = dblock; + break; + } +} + +void mock_layout_from_dump(uint32_t dbank, uint32_t dblock, + uint32_t *bank, uint32_t *block) +{ + formula_from_dump(dbank, dblock, bank, block); + *block = swap_cxt_block(*block); +} + +/* The store index of a page in the numbering the FTL sees */ +static size_t store_page(uint32_t bank, uint32_t page) +{ + uint32_t dbank, dblock; + + if (xlayout == XL_IDENTITY) + return (size_t)bank * BLOCKS * PPB + page; + mock_layout_to_dump(bank, page / PPB, &dbank, &dblock); + return ((size_t)dbank * SBLOCKS + dblock) * PPB + page % PPB; +} + +#define CXT_SIZE 0x800 +#define CXT_USEDCOUNT 0x1c +#define CXT_REMAP 0x2c +#define CXT_REMAP_N 820 +#define CXT_RING 0x694 +#define CXT_CKSUM 0x7f8 +#define VFL_SPARES 89 +#define VFL_FREE 0xfff0 +#define VFL_BAD 0xffff + +static uint16_t get16(const uint8_t *p, size_t off) +{ + return p[off] | p[off + 1] << 8; +} + +static void put16(uint8_t *p, size_t off, uint16_t v) +{ + p[off] = v; + p[off + 1] = v >> 8; +} + +static uint32_t get32(const uint8_t *p, size_t off) +{ + return get16(p, off) | (uint32_t)get16(p, off + 2) << 16; +} + +static void cxt_checksum(const uint8_t *c, uint32_t *sum, uint32_t *x) +{ + size_t off; + + *sum = *x = 0xAABBCCDD; + for (off = 0; off < CXT_CKSUM; off += 4) + { + *sum += get32(c, off); + *x ^= get32(c, off); + } +} + +static int cxt_ok(const uint8_t *c) +{ + uint32_t sum, x; + + cxt_checksum(c, &sum, &x); + return sum == get32(c, CXT_CKSUM) && x == get32(c, CXT_CKSUM + 4); +} + +static void cxt_seal(uint8_t *c) +{ + uint32_t sum, x; + + cxt_checksum(c, &sum, &x); + put16(c, CXT_CKSUM, sum); + put16(c, CXT_CKSUM + 2, sum >> 16); + put16(c, CXT_CKSUM + 4, x); + put16(c, CXT_CKSUM + 6, x >> 16); +} + +static uint32_t store_word(size_t g, int w) +{ + uint32_t v; + memcpy(&v, mock_meta + g * METASZ + 4 * w, 4); + return v; +} + +/* Store page g holds a VFL context with good checksums, as the FTL's + * ftl_vfl_read_cxt() accepts one */ +static int store_is_cxt(size_t g) +{ + uint32_t w2 = store_word(g, 2); + + return !is_erased(g) && page_rc(g) >= 0 && page_rc(g) != NAND_ECC_FAILED + && ((w2 >> 8) & 0xff) == 0x80 && (w2 & 0xff) == 0 + && cxt_ok(mock_data + g * PAGESIZE); +} + +/* The first good context of the commit at page of a dump block, or -1 */ +static long store_read_cxt(uint32_t dbank, uint32_t dblock, uint32_t page) +{ + size_t base = ((size_t)dbank * SBLOCKS + dblock) * PPB; + uint32_t i; + + for (i = page; i < page + 8 && i < PPB; i++) + if (store_is_cxt(base + i)) + return base + i; + return -1; +} + +/* The newest VFL context of a dump bank, found as ftl_vfl_open_bank() does */ +static const uint8_t *store_newest_cxt(uint32_t dbank) +{ + uint16_t ring[4]; + uint32_t i, block, last, best = 4, bestusn = 0xffffffff, w0; + long g = -1; + + for (block = 1; block < 200; block++) + if ((g = store_read_cxt(dbank, block, 0)) >= 0) + break; + if (g < 0) + return NULL; + for (i = 0; i < 4; i++) + ring[i] = get16(mock_data + g * PAGESIZE, CXT_RING + 2 * i); + for (i = 0; i < 4; i++) + { + if (ring[i] >= SBLOCKS || (g = store_read_cxt(dbank, ring[i], 0)) < 0) + continue; + w0 = store_word(g, 0); + if (w0 && w0 <= bestusn) + { + bestusn = w0; + best = i; + } + } + if (best == 4) + return NULL; + block = ring[best]; + last = 0; + for (i = 8; i < PPB; i += 8) + { + if (store_read_cxt(dbank, block, i) < 0) + break; + last = i; + } + g = store_read_cxt(dbank, block, last); + return g < 0 ? NULL : mock_data + g * PAGESIZE; +} + +/* A dump bank's block number as the new layout numbers it: where it now + * sits (for the ring, which is scanned), or with remap where the formula + * puts it (for a remap entry) */ +static uint16_t new_block(uint32_t dbank, uint16_t dblock, int remap) +{ + uint32_t bank, block; + + if (remap) + formula_from_dump(dbank, dblock, &bank, &block); + else + mock_layout_from_dump(dbank, dblock, &bank, &block); + return block; +} + +/* halves: every context keeps its bank, so each page is rewritten alone */ +static void rewrite_halves(void) +{ + size_t g, npages = (size_t)DUMP_BANKS * SBLOCKS * PPB; + uint32_t dbank, i; + uint8_t *c; + uint16_t e; + + for (g = 0; g < npages; g++) + { + if (!store_is_cxt(g)) + continue; + dbank = g / ((size_t)SBLOCKS * PPB); + c = mock_data + g * PAGESIZE; + for (i = 0; i < 2 * VFL_SPARES; i++) + { + e = get16(c, CXT_REMAP + 2 * i); + if (e != VFL_FREE && e != VFL_BAD) + put16(c, CXT_REMAP + 2 * i, new_block(dbank, e, 1)); + } + for (i = 0; i < 4; i++) + { + e = get16(c, CXT_RING + 2 * i); + if (e < SBLOCKS) + put16(c, CXT_RING + 2 * i, new_block(dbank, e, 0)); + } + cxt_seal(c); + } +} + +/* both: each new bank takes units 0 and 2 from dump bank ce and units 1 and + * 3 from dump bank ce + 2. The newest contexts of the two are merged (unit + * tables from each, header from the higher usn, ring from ce), and the + * merge replaces every context page of both, so whichever copy a VFL reads - + * including the secondary bank's, which now sit outside the ring - agrees. */ +static void rewrite_both(void) +{ + static uint8_t merged[CXT_SIZE]; + const uint8_t *src[2]; + size_t g, base, bankpages = (size_t)SBLOCKS * PPB; + uint32_t ce, u, i, dbank, nbank, nblock, side; + uint16_t e; + + for (ce = 0; ce < 2; ce++) + { + src[0] = store_newest_cxt(ce); + src[1] = store_newest_cxt(ce + 2); + if (!src[0] || !src[1]) + { + fprintf(stderr, "mock_open_layout: no VFL context on bank %u\n", + src[0] ? ce + 2 : ce); + exit(2); + } + /* The header - usn, FTL control blocks, update count and the rest - + * from the newer of the two, as the FTL takes its control blocks + * from the highest usn of any bank */ + memcpy(merged, src[get32(src[1], 0) > get32(src[0], 0)], CXT_SIZE); + memset(merged + CXT_REMAP, 0, 2 * CXT_REMAP_N); + for (u = 0; u < 4; u++) + { + const uint8_t *s = src[u % 2]; + uint32_t plane = u / 2; + + dbank = 2 * (u % 2) + ce; + put16(merged, CXT_USEDCOUNT + 2 * u, + get16(s, CXT_USEDCOUNT + 2 * plane)); + for (i = 0; i < VFL_SPARES; i++) + { + e = get16(s, CXT_REMAP + 2 * (plane * VFL_SPARES + i)); + if (e != VFL_FREE && e != VFL_BAD) + { + formula_from_dump(dbank, e, &nbank, &nblock); + e = nblock; + } + put16(merged, CXT_REMAP + 2 * (u * VFL_SPARES + i), e); + } + } + for (i = 0; i < 4; i++) + { + /* The ring is the primary's, which is where it sits */ + e = get16(src[0], CXT_RING + 2 * i); + put16(merged, CXT_RING + 2 * i, + e < SBLOCKS ? new_block(ce, e, 0) : e); + } + cxt_seal(merged); + + for (side = 0; side < 2; side++) + { + base = (ce + 2 * side) * bankpages; + for (g = base; g < base + bankpages; g++) + if (store_is_cxt(g)) + memcpy(mock_data + g * PAGESIZE, merged, CXT_SIZE); + } + } +} + +void mock_open_layout(const char *dir, const char *layout) +{ + int l = !strcmp(layout, "halves") ? XL_HALVES + : !strcmp(layout, "both") ? XL_BOTH : -1; + + if (l < 0) + { + fprintf(stderr, "mock_open_layout: unknown layout %s\n", layout); + exit(2); + } + /* Map and rewrite in the dump's own numbering */ + BANKS = DUMP_BANKS; + BLOCKS = DUMP_BLOCKS; + PPB = 128; + PAGESIZE = 2048; + mock_open(dir); + xlayout = l; + if (l == XL_HALVES) + rewrite_halves(); + else + rewrite_both(); + + if (l == XL_HALVES) + mock_geo = (struct nand_geometry){ + .banks = 4, .blocks = 4096, .pagesperblock = 128, .planes = 2, + .userblocks = 3872, .vflspares = 89, .twoplane = false, + .pagesize = 2048, .mode = 9, .layout = NAND_LAYOUT_HALVES, + .validated = true, + }; + else + mock_geo = (struct nand_geometry){ + .banks = 2, .blocks = 8192, .pagesperblock = 128, .planes = 4, + .userblocks = 7744, .vflspares = 89, .twoplane = false, + .pagesize = 2048, .mode = 4, .layout = NAND_LAYOUT_BOTH, + .validated = true, + }; + BANKS = mock_geo.banks; + BLOCKS = mock_geo.blocks; +} diff --git a/utils/ipodnano3g/ftltest/mock_nand.h b/utils/ipodnano3g/ftltest/mock_nand.h new file mode 100644 index 0000000000..72001495cb --- /dev/null +++ b/utils/ipodnano3g/ftltest/mock_nand.h @@ -0,0 +1,85 @@ +/* Host mock of the Nano 3G NAND driver. */ +#ifndef MOCK_NAND_H +#define MOCK_NAND_H + +#include + +/* The backing store, mapped copy-on-write: writes land in RAM and never + * reach the dump files. Page data is NAND_PAGE_SIZE per page; meta is 16 + * bytes per page - 3 spare words then the int32 nand_read_page() result. */ +extern uint8_t *mock_data; + +/* What nand_get_geometry() returns: the dumped 4GB unit's chip, which tests + * may alter before ftl_init() */ +struct nand_geometry; +extern struct nand_geometry mock_geo; +extern uint8_t *mock_meta; + +/* Counters, for tests to assert on. Reset with mock_reset_stats(). */ +extern unsigned long mock_reads; +extern unsigned long mock_read_runs, mock_read_run_pages; +extern unsigned long mock_writes; +extern unsigned long mock_writes_by_type[256]; /* by spare type */ +extern unsigned long mock_erases; +/* Violations of real NAND's rules, which the FTL must never commit: */ +extern unsigned long mock_reprogram; /* programmed a non-erased page */ +extern unsigned long mock_nonsequential; /* skipped a page within a block */ +extern unsigned long mock_runs, mock_run_pages; /* nand_write_pages calls */ +extern unsigned long mock_twoplane_runs, mock_twoplane_pages; +extern unsigned long mock_bad_twoplane; /* a pair the chip could not take */ + +/* Fault injection: make a physical block refuse programs and/or erases, as a + * worn-out block does. NAND_OP_FAILED is what the real driver returns when + * the chip reports a failure. */ +#define MOCK_FAIL_WRITE 1 +#define MOCK_FAIL_ERASE 2 +void mock_set_bad(uint32_t bank, uint32_t block, int how); +void mock_clear_bad(void); +extern unsigned long mock_write_fails, mock_erase_fails; + +/* Crash injection: after this many more programs and erases, the next one + * does not happen and longjmp(mock_crash_jmp, 1) is taken instead, as if + * power were cut just before it. 0 disables. */ +#include +extern unsigned long mock_crash_after; +extern jmp_buf mock_crash_jmp; +extern void *mock_crash_bt[32]; /* where the last cut happened */ +extern int mock_crash_btn; +extern int mock_crash_torn; /* leave the cut operation half done */ + +void mock_open(const char *dir); +/* A blank, erased medium with mock_geo's geometry, for ftl_hook_format() */ +void mock_open_blank(void); +void mock_reset_stats(void); + +/* The dump presented as a chip of another layout would hold it: "halves" + * (mode 9: 4 banks of 4096 blocks, unit u of VFL block v at v + u * 2048) or + * "both" (mode 4: 2 banks of 8192 blocks, 4 units, unit u of v at + * 2v + (u & 1) + ((u & 2) ? 4096 : 0), taking dump banks ce and ce + 2). + * Every superblock keeps its pages in order, except that on each new bank + * block 1 and the formula's place for the primary dump bank's block 1 (2048 + * or 4096) are swapped, so the context ring starts within blocks 1..199 as + * on a chip Apple formatted. The rewritten rings name blocks where they now + * sit; remap entries name replaced blocks by the formula alone, as the FTL + * computes them (their contents are never read). Sets mock_geo, and every NAND + * call, mock_set_bad() and crash injection then use the new numbering. The + * VFL contexts are rewritten in the copy-on-write mapping to hold the new + * block numbers; for "both" the newest contexts of the two dump banks are + * merged into every context page of both. mock_data and mock_meta stay in + * the dump's layout. */ +void mock_open_layout(const char *dir, const char *layout); + +/* Block translation of the layout last opened, the identity after + * mock_open() or mock_open_blank(): a block as the FTL numbers it to the + * dump's bank and block holding its pages, and back */ +void mock_layout_to_dump(uint32_t bank, uint32_t block, + uint32_t *dbank, uint32_t *dblock); +void mock_layout_from_dump(uint32_t dbank, uint32_t dblock, + uint32_t *bank, uint32_t *block); + +/* Write the current state of the mock out, for post-mortem inspection. The + * files are the backing store, so after mock_open_layout() they are in the + * dump's layout, not the one the FTL saw. Returns 0, or -1 with errno set. */ +int mock_snapshot(const char *dir); + +#endif diff --git a/utils/ipodnano3g/ftltest/replay.c b/utils/ipodnano3g/ftltest/replay.c new file mode 100644 index 0000000000..ea0b9db902 --- /dev/null +++ b/utils/ipodnano3g/ftltest/replay.c @@ -0,0 +1,140 @@ +/* Replay a block-layer write trace through the FTL on the mock NAND and + * report what it cost. The trace is Linux's block_rq_issue tracepoint from a + * guest writing to a disk with 4096-byte logical sectors, so each 512-byte + * trace sector is a quarter of an FTL sector. Writes are split as Rockbox's + * USB storage does, into calls of at most WRITE_BUFFER_SIZE (24 KB). + * + * replay TRACE [DUMPDIR] + */ +#include +#include +#include +#include "nand-target.h" +#include "ftl-target.h" +#include "mock_nand.h" + +#define CHUNK_SECTORS (24 * 1024 / NAND_PAGE_SIZE) /* 11: 24 KB, whole */ +#define SEC_PER_4K 2 + +/* Counters an instrumented FTL build may provide */ +extern unsigned long st_merge __attribute__((weak)), + st_merge_wear __attribute__((weak)), st_merge_full __attribute__((weak)), + st_seqadopt __attribute__((weak)), st_seqfill __attribute__((weak)), + st_sync __attribute__((weak)), st_bound_logs __attribute__((weak)), + st_bound_pool __attribute__((weak)), st_append __attribute__((weak)), + st_log_alloc __attribute__((weak)); + +static void report(const char *phase, unsigned long hostpages, + unsigned long calls) +{ + unsigned long data = mock_writes_by_type[0x40] + mock_writes_by_type[0x41]; + unsigned long ctrl = mock_writes - data; + + printf("%-8s host pages %7lu in %6lu calls | programs %7lu " + "(data %7lu = %.2fx host, ctrl %5lu) reads %7lu erases %5lu | " + "commits %lu, marks %lu\n", + phase, hostpages, calls, mock_writes, data, + hostpages ? (double)data / hostpages : 0.0, ctrl, mock_reads, + mock_erases, mock_writes_by_type[0x43], mock_writes_by_type[0x4f]); + printf(" program runs %lu (%.1f pages each), two-plane %lu " + "(%.0f%% of pages), bad pairs %lu\n", mock_runs, + mock_runs ? (double)mock_run_pages / mock_runs : 0.0, + mock_twoplane_runs, + mock_run_pages ? 100.0 * mock_twoplane_pages / mock_run_pages : 0.0, + mock_bad_twoplane); + printf(" read runs %lu (%.1f pages each)\n", mock_read_runs, + mock_read_runs ? (double)mock_read_run_pages / mock_read_runs : 0.0); + if (&st_merge) + { + printf(" appends %lu, logs opened %lu | syncs %lu (bound: logs %lu, " + "pool %lu) | merges %lu (whole-block %lu, wear %lu, folds %lu) " + "| sequential adoptions %lu filling %lu pages\n", + st_append, st_log_alloc, st_sync, st_bound_logs, st_bound_pool, + st_merge, st_merge_full, st_merge_wear, + st_merge - st_merge_full - st_merge_wear, st_seqadopt, st_seqfill); + st_append = st_log_alloc = st_sync = st_bound_logs = st_bound_pool = 0; + st_merge = st_merge_full = st_merge_wear = st_seqadopt = st_seqfill = 0; + } +} + +int main(int argc, char **argv) +{ + static uint8_t buf[32 * NAND_PAGE_SIZE]; + char line[512]; + FILE *f; + unsigned long hostpages = 0, calls = 0, reqs = 0; + const char *phase = "copy"; + + if (argc < 2) + { + fprintf(stderr, "usage: replay TRACE [DUMPDIR]\n"); + return 2; + } + f = fopen(argv[1], "r"); + if (!f) + { + perror(argv[1]); + return 1; + } + mock_open(argc > 2 ? argv[2] : "../n3g-dump"); + if (ftl_init() || ftl_sync() || ftl_init()) + { + puts("mount/settle failed"); + return 1; + } + mock_reset_stats(); + memset(buf, 0xa5, sizeof(buf)); + + while (fgets(line, sizeof(line), f)) + { + char *p, rwbs[8]; + unsigned long long sector; + unsigned long nr, bytes; + uint32_t lpn, n, done; + + if (strstr(line, "MARK COPY_DONE")) + { + if (ftl_sync()) + puts("sync failed"); + report(phase, hostpages, calls); + mock_reset_stats(); + hostpages = calls = 0; + phase = "delete"; + continue; + } + if (strstr(line, "MARK DELETE_DONE")) + break; + p = strstr(line, "block_rq_issue: "); + if (!p) + continue; + if (sscanf(p, "block_rq_issue: %*s %7s %lu () %llu + %lu", + rwbs, &bytes, §or, &nr) != 4) + continue; + if (!strchr(rwbs, 'W') || !nr) + continue; + reqs++; + lpn = sector / 4; + n = nr / 4; + for (done = 0; done < n; done += CHUNK_SECTORS / SEC_PER_4K * SEC_PER_4K) + { + uint32_t c = n - done; + + if (c > CHUNK_SECTORS / SEC_PER_4K * SEC_PER_4K) + c = CHUNK_SECTORS / SEC_PER_4K * SEC_PER_4K; + if (ftl_write(lpn + done, c, buf)) + { + printf("ftl_write(%u, %u) failed\n", lpn + done, c); + return 1; + } + calls++; + hostpages += c; + } + } + if (ftl_sync()) + puts("final sync failed"); + report(phase, hostpages, calls); + printf("%lu write requests; NAND rule violations: reprogram %lu, " + "nonsequential %lu\n", reqs, mock_reprogram, mock_nonsequential); + fclose(f); + return 0; +} diff --git a/utils/ipodnano3g/ftltest/stubs/config.h b/utils/ipodnano3g/ftltest/stubs/config.h new file mode 100644 index 0000000000..1ffee4ea3c --- /dev/null +++ b/utils/ipodnano3g/ftltest/stubs/config.h @@ -0,0 +1 @@ +/* Host test stub: ftl-nano3g.c needs nothing from config.h */ diff --git a/utils/ipodnano3g/ftltest/stubs/kernel.h b/utils/ipodnano3g/ftltest/stubs/kernel.h new file mode 100644 index 0000000000..515949ffa0 --- /dev/null +++ b/utils/ipodnano3g/ftltest/stubs/kernel.h @@ -0,0 +1,4 @@ +struct mutex { int locked; }; +static inline void mutex_init(struct mutex *m) { m->locked = 0; } +static inline void mutex_lock(struct mutex *m) { m->locked = 1; } +static inline void mutex_unlock(struct mutex *m) { m->locked = 0; } diff --git a/utils/ipodnano3g/ftltest/stubs/logf.h b/utils/ipodnano3g/ftltest/stubs/logf.h new file mode 100644 index 0000000000..3ffc310a67 --- /dev/null +++ b/utils/ipodnano3g/ftltest/stubs/logf.h @@ -0,0 +1,2 @@ +#include +#define logf(...) (fprintf(stderr, "logf: " __VA_ARGS__), fputc('\n', stderr)) diff --git a/utils/ipodnano3g/ftltest/stubs/panic.h b/utils/ipodnano3g/ftltest/stubs/panic.h new file mode 100644 index 0000000000..fd04ba2e3e --- /dev/null +++ b/utils/ipodnano3g/ftltest/stubs/panic.h @@ -0,0 +1,4 @@ +#include +#include +#define panicf(...) (fprintf(stderr, "panicf: " __VA_ARGS__), \ + fputc('\n', stderr), abort()) diff --git a/utils/ipodnano3g/ftltest/stubs/storage.h b/utils/ipodnano3g/ftltest/stubs/storage.h new file mode 100644 index 0000000000..c8a60e3edf --- /dev/null +++ b/utils/ipodnano3g/ftltest/stubs/storage.h @@ -0,0 +1 @@ +#define STORAGE_ALIGN_ATTR __attribute__((aligned(16))) diff --git a/utils/ipodnano3g/ftltest/stubs/system.h b/utils/ipodnano3g/ftltest/stubs/system.h new file mode 100644 index 0000000000..c34831d138 --- /dev/null +++ b/utils/ipodnano3g/ftltest/stubs/system.h @@ -0,0 +1,4 @@ +#ifndef MIN +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif +#define ARRAYLEN(a) (sizeof(a) / sizeof((a)[0])) diff --git a/utils/ipodnano3g/ftltest/test_badblock.c b/utils/ipodnano3g/ftltest/test_badblock.c new file mode 100644 index 0000000000..2dcb8df735 --- /dev/null +++ b/utils/ipodnano3g/ftltest/test_badblock.c @@ -0,0 +1,154 @@ +/* Bad-block handling: a physical block that refuses programs or erases must + * be replaced from the VFL's reserved spares, the write must still succeed, + * and the replacement must survive a remount - which only happens if the VFL + * context was committed. Ported alongside ftl-nano2g.c's VFL write side. + */ +#include +#include +#include +#include "nand-target.h" +#include "ftl-target.h" +#include "mock_nand.h" + +#define BANKS 4 +#define BLOCKS 4096 +#define PPB 128 +#define PLANES 2 +#define SBPAGES (PPB * PLANES * BANKS) +#define POOL 20 + +static int fails; + +static void check(const char *what, int ok) +{ + printf("%s %s\n", ok ? "PASS" : "FAIL", what); + if (!ok) + fails++; +} + +/* The physical (bank, block) behind page v of superblock sb */ +static int phys_of(uint32_t sb, uint32_t v, uint32_t *bank, uint32_t *block) +{ + uint32_t page; + + if (ftl_sb_locate(sb, v, bank, &page)) + return -1; + *block = page / PPB; + return 0; +} + +int main(int argc, char **argv) +{ + static uint8_t blk[SBPAGES * NAND_PAGE_SIZE]; /* one superblock */ + static uint8_t buf[SBPAGES * NAND_PAGE_SIZE]; + uint16_t psb[POOL], pec[POOL]; + uint32_t n, i, victim = 0, vlow = 0xffff, bank = 0, block = 0, lblock = 800; + uint32_t vblock; + int entry_before, entry_after; + + mock_open(argc > 1 ? argv[1] : "../n3g-dump"); + if (ftl_init()) { puts("mount failed"); return 1; } + check("mounts writeable", ftl_hook_writeable()); + check("settles", ftl_hook_settle() == 0); + + /* The next allocation takes the least-worn pool block, so that is the + * one a write will land in - make its first physical block refuse + * programs, as a worn-out block does. */ + n = ftl_pool_snapshot(psb, pec, POOL); + check("pool snapshot", n == POOL); + for (i = 0; i < n; i++) + if (pec[i] < vlow) { vlow = pec[i]; victim = psb[i]; } + check("found the block the next write will use", victim != 0); + check("located it physically", phys_of(victim, 0, &bank, &block) == 0); + vblock = victim * PLANES; /* v=0 is plane 0 of the superblock */ + entry_before = ftl_vfl_pool_entry(bank, block); + printf(" victim sb %u -> bank %u block %u (pool entry %d, ec %u)\n", + victim, bank, block, entry_before, vlow); + + mock_clear_bad(); + mock_set_bad(bank, block, MOCK_FAIL_WRITE); + + /* A whole-block write, so it goes through ftl_merge()'s retry path. + * A failed program into a live log panics by design instead. */ + for (i = 0; i < sizeof(blk); i++) + blk[i] = (uint8_t)(i * 7 + 3); + check("a write into a block that refuses programs still succeeds", + ftl_write(lblock * SBPAGES, SBPAGES, blk) == 0); + check("the mock really did refuse a program", mock_write_fails > 0); + check("the data reads back", + ftl_read(lblock * SBPAGES, SBPAGES, buf) == 0 + && !memcmp(buf, blk, sizeof(blk))); + + /* The bad virtual block must now be remapped onto a reserved spare */ + { + int found = -1; + uint32_t b; + + for (b = 0; b < BLOCKS; b++) + if (ftl_vfl_pool_entry(bank, b) == (int)vblock) + { + found = b; + break; + } + printf(" vblock %u is now served by physical block %d\n", + vblock, found); + check("the bad block was remapped onto a spare", found > 0); + } + + /* ...and the remap has to survive a remount, which means the VFL + * context was committed to flash */ + check("sync and remount", ftl_sync() == 0 && ftl_init() == 0); + { + int found = -1; + uint32_t b; + + for (b = 0; b < BLOCKS; b++) + if (ftl_vfl_pool_entry(bank, b) == (int)vblock) + { + found = b; + break; + } + check("the remap survived the remount (VFL context committed)", + found > 0); + } + entry_after = ftl_vfl_pool_entry(bank, block); + check("the old slot is unchanged in the pool table", + entry_after == entry_before); + check("the data still reads back after the remount", + ftl_read(lblock * SBPAGES, SBPAGES, buf) == 0 + && !memcmp(buf, blk, sizeof(blk))); + + /* A block that refuses erases must be replaced too. Erases happen when + * the *old* superblock is released after a merge, so the block to kill + * is the one that currently holds the logical block being written. */ + mock_clear_bad(); + { + uint32_t page2; + + check("locate the block about to be released", + ftl_locate((lblock + 1) * SBPAGES, &bank, &page2) == 0); + block = page2 / PPB; + printf(" lblock %u currently lives in bank %u block %u\n", + lblock + 1, bank, block); + mock_set_bad(bank, block, MOCK_FAIL_ERASE); + for (i = 0; i < sizeof(blk); i++) + blk[i] = (uint8_t)(i * 13 + 1); + /* The erase comes when the old superblock is released, which for a + * partial write happens at the sync that folds the log. */ + check("a write that must erase a dead block still succeeds", + ftl_write((lblock + 1) * SBPAGES, 16, blk) == 0 + && ftl_sync() == 0); + check("the mock really did refuse an erase", mock_erase_fails > 0); + check("that data reads back too", + ftl_read((lblock + 1) * SBPAGES, 16, buf) == 0 + && !memcmp(buf, blk, 16 * NAND_PAGE_SIZE)); + } + + mock_clear_bad(); + check("final sync and remount", ftl_sync() == 0 && ftl_init() == 0); + check("everything still readable", + ftl_read(lblock * SBPAGES, 16, buf) == 0); + + printf(fails ? "SOME CHECKS FAILED\n" : "ALL PASS\n"); + return fails ? 1 : 0; +} diff --git a/utils/ipodnano3g/ftltest/test_crash.c b/utils/ipodnano3g/ftltest/test_crash.c new file mode 100644 index 0000000000..7a7f5dbc83 --- /dev/null +++ b/utils/ipodnano3g/ftltest/test_crash.c @@ -0,0 +1,232 @@ +/* Power-loss stress: random writes and syncs over a group of logical blocks, + * with the mock cutting power at a random program or erase - in the middle + * of a log append, a fold, a sequential adoption, a wear-levelling move or a + * commit. After each cut the FTL is mounted afresh and every sector in the + * group must hold what the last acknowledged write put there; a sector in + * the write that was cut may hold either its old or its new contents. + * + * test_crash [DUMPDIR] [CUTS] [SEED] [MAXOPS] [TORN] + */ +#include +#include +#include +#include "nand-target.h" +#include "ftl-target.h" +#include "mock_nand.h" + +#define SBPAGES 1024 +#define BASE 1000 /* first logical block used */ +#define NLB 40 /* logical blocks used */ +#define NLPN (NLB * SBPAGES) +#define CHUNK 12 /* Rockbox's USB write size */ + +static uint32_t model[NLPN]; /* tag of each sector, 0 = unwritten */ +static uint32_t pend_lo, pend_n, pend_tag, pend_base; +static uint8_t page[CHUNK * NAND_PAGE_SIZE]; +static uint32_t rnd_state; + +static uint32_t rnd(void) +{ + rnd_state = rnd_state * 1103515245u + 12345u; + return rnd_state >> 8; +} + +static uint32_t tag_of(const uint8_t *p) +{ + uint32_t t; + memcpy(&t, p, 4); + return t; +} + +/* Write n sectors from lpn, tag = (write id << 12) + offset, in USB chunks */ +static int do_write(uint32_t lo, uint32_t n, uint32_t id) +{ + uint32_t done, k, c; + + pend_lo = lo; + pend_n = n; + pend_tag = id; + pend_base = lo; + for (done = 0; done < n; done += c) + { + c = n - done < CHUNK ? n - done : CHUNK; + for (k = 0; k < c; k++) + { + uint32_t t = (id << 12) + done + k, w; + for (w = 0; w < NAND_PAGE_SIZE; w += 4) + memcpy(page + k * NAND_PAGE_SIZE + w, &t, 4); + } + { + int rc = ftl_write(BASE * SBPAGES + lo + done, c, page); + if (rc) + { + printf(" ftl_write(+%u, %u) = %d\n", lo + done, c, rc); + return -1; + } + } + /* Acknowledged: this chunk is now owed to the host */ + for (k = 0; k < c; k++) + model[lo + done + k] = (id << 12) + done + k; + pend_lo = lo + done + c; + pend_n = n - done - c; + } + pend_n = 0; + return 0; +} + +static int verify(uint32_t *bad_out, uint32_t *ambig_out) +{ + static uint8_t buf[NAND_PAGE_SIZE]; + uint32_t i, got, bad = 0, ambig = 0; + + for (i = 0; i < NLPN; i++) + { + if (ftl_read(BASE * SBPAGES + i, 1, buf)) + { + bad++; + continue; + } + got = tag_of(buf); + if (model[i] == 0 && got == 0xffffffff) + got = 0; + if (got == model[i]) + continue; + if (pend_n && i >= pend_lo && i < pend_lo + pend_n + && got == (pend_tag << 12) + (i - pend_base)) + { + ambig++; + model[i] = got; + continue; + } + if (bad < 5) + printf(" lpn +%u: got %08x want %08x\n", i, got, model[i]); + bad++; + } + *bad_out = bad; + *ambig_out = ambig; + return bad ? -1 : 0; +} + +int main(int argc, char **argv) +{ + static unsigned long cuts, cut; + static uint32_t id = 1, bad, ambig, totalbad, syncs, writes; + static int fails; + + static unsigned long maxops; + + cuts = argc > 2 ? strtoul(argv[2], NULL, 0) : 300; + maxops = argc > 4 ? strtoul(argv[4], NULL, 0) : 6000; + mock_crash_torn = argc > 5 ? atoi(argv[5]) : 0; + + rnd_state = argc > 3 ? strtoul(argv[3], NULL, 0) : 12345; + if (ftl_hook_open(argc > 1 ? argv[1] : "../n3g-dump")) + { + puts("cannot open the medium"); + return 2; + } + if (ftl_init() || ftl_sync() || ftl_init()) + { + puts("mount failed"); + return 1; + } + /* Start from known contents: every sector of the group written once */ + { + uint32_t lb; + for (lb = 0; lb < NLB; lb++) + if (do_write(lb * SBPAGES, SBPAGES, id++)) + { + puts("initial fill failed"); + return 1; + } + if (ftl_sync()) + { + puts("initial sync failed"); + return 1; + } + } + + for (cut = 0; cut < cuts; cut++) + { + mock_crash_after = 1 + rnd() % maxops; + if (!setjmp(mock_crash_jmp)) + { + for (;;) + { + uint32_t r = rnd() % 100, lb = rnd() % NLB; + + if (r < 75) /* scattered small write */ + { + uint32_t n = 1 + rnd() % 40; + uint32_t lo = lb * SBPAGES + rnd() % (SBPAGES - n); + if (do_write(lo, n, id++)) break; + writes++; + } + else if (r < 90) /* a stretch written in order */ + { + uint32_t n = 64 + rnd() % 900; + uint32_t lo = lb * SBPAGES + rnd() % (SBPAGES - n); + if (do_write(lo, n, id++)) break; + writes++; + } + else if (r < 95) /* a whole block */ + { + if (do_write(lb * SBPAGES, SBPAGES, id++)) break; + writes++; + } + else + { + { int rc = ftl_sync(); if (rc) { printf(" ftl_sync() = %d\n", rc); break; } } + syncs++; + } + } + printf(" cut %lu: a write or sync failed without a cut\n", cut); + fails++; + break; + } + /* Power was cut. Mount afresh and check. */ + mock_crash_after = 0; + if (ftl_init()) + { + printf(" cut %lu: mount failed\n", cut); + fails++; + break; + } + { + char msg[256]; + if (ftl_hook_check(msg, sizeof(msg))) + { + int k; + printf(" cut %lu: FTL state inconsistent after remount: %s\n", cut, msg); + printf(" the cut was at:"); + for (k = 0; k < mock_crash_btn; k++) + printf(" %p", mock_crash_bt[k]); + printf("\n"); + fails++; + break; + } + } + verify(&bad, &ambig); + if (bad) + { + int k; + printf(" cut %lu: %u sectors wrong; pending +%u n %u tag %u\n", cut, bad, pend_base, pend_n, pend_tag); + printf(" the cut was at:"); + for (k = 0; k < mock_crash_btn; k++) + printf(" %p", mock_crash_bt[k]); + printf("\n"); + totalbad += bad; + fails++; + break; + } + pend_n = 0; + } + printf(" %lu cuts, %u writes, %u syncs, %u sectors wrong\n", + cut, writes, syncs, totalbad); + printf("%s every acknowledged write survives every cut\n", + fails ? "FAIL" : "PASS"); + printf("NAND rules: reprogram %lu, nonsequential %lu\n", + mock_reprogram, mock_nonsequential); + printf(fails ? "SOME CHECKS FAILED\n" : "ALL PASS\n"); + return fails ? 1 : 0; +} diff --git a/utils/ipodnano3g/ftltest/test_eligible.c b/utils/ipodnano3g/ftltest/test_eligible.c new file mode 100644 index 0000000000..c9690f7f2e --- /dev/null +++ b/utils/ipodnano3g/ftltest/test_eligible.c @@ -0,0 +1,118 @@ +/* Unclean-stop safety of pool allocation: a block released since the last + * commit is still the committed map's copy of some logical block, so + * ftl_pool_alloc() must never have to hand one out. ftl_write_merge() + * commits early to guarantee that; its bound has to count open logs too, + * since they occupy pool blocks. The write sequence below drives the pool + * to "no block free at the last commit" without tripping a bound that + * ignores ftl_nlogs. + */ +#include +#include +#include +#include "nand-target.h" +#include "ftl-target.h" +#include "mock_nand.h" + +#define SBPAGES 1024 +#define BASE 1000 /* first logical block used */ + +static int fails; + +static void check(const char *what, int ok) +{ + printf("%s %s\n", ok ? "PASS" : "FAIL", what); + if (!ok) + fails++; +} + +static void fill(uint8_t *buf, uint32_t lpn, uint32_t n, uint8_t salt) +{ + uint32_t i, j; + + for (i = 0; i < n; i++) + for (j = 0; j < NAND_PAGE_SIZE; j++) + buf[i * NAND_PAGE_SIZE + j] = (uint8_t)(lpn + i + j * 3 + salt); +} + +/* Blocks the next allocation may take without breaking the invariant */ +static uint32_t safe_free(void) +{ + uint32_t freecount, fresh, nlogs; + + ftl_pool_state(&freecount, &fresh, &nlogs); + printf(" free %u fresh %u logs %u\n", freecount, fresh, nlogs); + return freecount > fresh ? freecount - fresh : 0; +} + +int main(int argc, char **argv) +{ + static uint8_t blk[SBPAGES * NAND_PAGE_SIZE], buf[SBPAGES * NAND_PAGE_SIZE]; + uint32_t i, lpn, minsafe = 0xffffffff, bad = 0; + int ok = 1; + + mock_open(argc > 1 ? argv[1] : "../n3g-dump"); + if (ftl_init()) { puts("mount failed"); return 1; } + check("mounts writeable", ftl_hook_writeable()); + check("settles", ftl_hook_settle() == 0); + + /* Eight blocks that each get a log and then a whole-block write, which + * folds the log: two releases per block */ + for (i = 0; i < 8 && ok; i++) + { + lpn = (BASE + i) * SBPAGES; + fill(blk, lpn + 5, 16, 1); + ok = ftl_write(lpn + 5, 16, blk) == 0; + fill(blk, lpn, SBPAGES, 2); + ok = ok && ftl_write(lpn, SBPAGES, blk) == 0; + } + check("eight log-then-whole-block rewrites", ok); + + /* One log filled in order over two calls, adopted outright: one release */ + lpn = (BASE + 8) * SBPAGES; + fill(blk, lpn, SBPAGES, 3); + check("a sequential log filled in two halves", + ftl_write(lpn, SBPAGES / 2, blk) == 0 + && ftl_write(lpn + SBPAGES / 2, SBPAGES / 2, + blk + SBPAGES / 2 * NAND_PAGE_SIZE) == 0); + + /* Open logs on four more blocks. Each takes a pool block. */ + for (i = 9; i < 13 && ok; i++) + { + uint32_t s = safe_free(); + + if (s < minsafe) + minsafe = s; + lpn = (BASE + i) * SBPAGES; + fill(blk, lpn + 7, 16, 4); + ok = ftl_write(lpn + 7, 16, blk) == 0; + } + check("four small writes opening logs", ok); + printf(" fewest safe blocks before an allocation: %u\n", minsafe); + + /* And an unclean stop now must lose nothing */ + check("remount without syncing", ftl_init() == 0); + for (i = 0; i < 8; i++) + { + lpn = (BASE + i) * SBPAGES; + fill(blk, lpn, SBPAGES, 2); + if (ftl_read(lpn, SBPAGES, buf) || memcmp(buf, blk, sizeof(blk))) + bad++; + } + lpn = (BASE + 8) * SBPAGES; + fill(blk, lpn, SBPAGES, 3); + if (ftl_read(lpn, SBPAGES, buf) || memcmp(buf, blk, sizeof(blk))) + bad++; + for (i = 9; i < 13; i++) + { + lpn = (BASE + i) * SBPAGES; + fill(blk, lpn + 7, 16, 4); + if (ftl_read(lpn + 7, 16, buf) || memcmp(buf, blk, 16 * NAND_PAGE_SIZE)) + bad++; + } + printf(" %u of 13 blocks wrong after the unclean remount\n", bad); + check("everything written reads back after an unclean stop", bad == 0); + + check("final sync", ftl_sync() == 0); + printf(fails ? "SOME CHECKS FAILED\n" : "ALL PASS\n"); + return fails ? 1 : 0; +} diff --git a/utils/ipodnano3g/ftltest/test_format.c b/utils/ipodnano3g/ftltest/test_format.c new file mode 100644 index 0000000000..186f4bc9b0 --- /dev/null +++ b/utils/ipodnano3g/ftltest/test_format.c @@ -0,0 +1,239 @@ +/* The FTL on a formatted blank medium of a chosen geometry, for chips no + * dump exists of - above all 4KiB pages, where one NAND page holds two + * sectors. Writes of whole superblocks, odd sectors and pages split across + * two calls must read back, before and after a sync and remount, and after + * a remount without one; the medium must never be programmed against NAND's + * rules. + * + * test_format blank:PRESET (see ftl_hook_open) + */ +#include +#include +#include +#include "nand-target.h" +#include "ftl-target.h" +#include "mock_nand.h" + +#define LBSECTORS 1024 /* sectors in a logical block, every preset */ +#define BASE 900 /* first logical block used */ +#define NLB 8 +#define NSEC (NLB * LBSECTORS) + +static uint16_t tag[NSEC]; /* what each sector holds; 0 = never written */ +static uint8_t buf[LBSECTORS * NAND_PAGE_SIZE]; +static int fails; + +static void check(const char *what, int ok) +{ + printf("%s %s\n", ok ? "PASS" : "FAIL", what); + if (!ok) + fails++; +} + +static void fill(uint8_t *p, uint32_t sector, uint16_t t) +{ + uint32_t j; + + for (j = 0; j < NAND_PAGE_SIZE; j += 4) + { + uint32_t w = sector * 2654435761u ^ t * 40503u ^ j; + memcpy(p + j, &w, 4); + } +} + +static int put(uint32_t off, uint32_t n, uint16_t t) +{ + uint32_t i; + + for (i = 0; i < n; i++) + fill(buf + i * NAND_PAGE_SIZE, BASE * LBSECTORS + off + i, t); + if (ftl_write(BASE * LBSECTORS + off, n, buf)) + return -1; + for (i = 0; i < n; i++) + tag[off + i] = t; + return 0; +} + +/* Every sector of the group, one read call per logical block */ +static int verify(void) +{ + static uint8_t want[NAND_PAGE_SIZE]; + uint32_t lb, i, bad = 0; + + for (lb = 0; lb < NLB; lb++) + { + if (ftl_read((BASE + lb) * LBSECTORS, LBSECTORS, buf)) + return -1; + for (i = 0; i < LBSECTORS; i++) + { + uint32_t s = lb * LBSECTORS + i; + + if (tag[s]) + fill(want, BASE * LBSECTORS + s, tag[s]); + else + memset(want, 0xff, NAND_PAGE_SIZE); + if (memcmp(buf + i * NAND_PAGE_SIZE, want, NAND_PAGE_SIZE)) + bad++; + } + } + if (bad) + printf(" %u sectors wrong\n", bad); + return bad ? -1 : 0; +} + +/* Single sectors one call at a time, and odd runs, read back singly */ +static int verify_single(uint32_t off, uint32_t n) +{ + static uint8_t one[3 * NAND_PAGE_SIZE], want[NAND_PAGE_SIZE]; + uint32_t i, k, len; + + for (i = 0; i < n; i += len) + { + len = 1 + (i % 3); + if (i + len > n) + len = n - i; + if (ftl_read(BASE * LBSECTORS + off + i, len, one)) + return -1; + for (k = 0; k < len; k++) + { + uint32_t s = off + i + k; + if (tag[s]) + fill(want, BASE * LBSECTORS + s, tag[s]); + else + memset(want, 0xff, NAND_PAGE_SIZE); + if (memcmp(one + k * NAND_PAGE_SIZE, want, NAND_PAGE_SIZE)) + return -1; + } + } + return 0; +} + +int main(int argc, char **argv) +{ + const char *spec = argc > 1 ? argv[1] : "blank:4k2"; + char msg[256]; + uint32_t i, r = 12345; + uint16_t t = 1; + int ok; + + if (ftl_hook_open(spec)) + { + printf("FAIL format %s\n", spec); + return 1; + } + printf(" %s: %u banks, %u-byte pages\n", spec, mock_geo.banks, + mock_geo.pagesize); + check("format programs every page in order", + mock_nonsequential == 0 && mock_reprogram == 0); + mock_reset_stats(); + check("formatted volume mounts", ftl_init() == 0); + check("... writable", ftl_hook_writeable()); + check("... sector count", ftl_num_sectors() + == mock_geo.userblocks / mock_geo.planes * LBSECTORS); + check("unwritten sectors read erased", verify() == 0); + + check("whole logical block", put(0, LBSECTORS, t++) == 0); + check("one sector at the start of a page", put(LBSECTORS + 10, 1, t++) + == 0); + check("one sector at the end of a page", put(LBSECTORS + 21, 1, t++) + == 0); + check("an odd run crossing pages", put(LBSECTORS + 101, 13, t++) == 0); + check("an odd run crossing logical blocks", + put(2 * LBSECTORS - 7, 15, t++) == 0); + check("two halves of one page in two calls", + put(3 * LBSECTORS + 50, 1, t++) == 0 + && put(3 * LBSECTORS + 51, 1, t++) == 0); + check("reads back", verify() == 0); + check("reads back a sector at a time", verify_single(LBSECTORS, 200) == 0 + && verify_single(2 * LBSECTORS - 16, 40) == 0); + + /* Random small writes, enough to fill and merge logs */ + ok = 1; + for (i = 0; i < 3000 && ok; i++) + { + uint32_t n, off; + + r = r * 1103515245u + 12345u; + n = 1 + (r >> 16) % 12; + r = r * 1103515245u + 12345u; + off = (r >> 8) % (NSEC - n); + ok = put(off, n, t++) == 0; + } + check("3000 random writes of 1-12 sectors", ok); + check("... read back", verify() == 0); + check("sync", ftl_sync() == 0); + check("remount", ftl_init() == 0); + check("... read back", verify() == 0); + check("... consistent", ftl_hook_check(msg, sizeof(msg)) == 0); + + /* Writes left uncommitted are recovered by the restore */ + ok = 1; + for (i = 0; i < 500 && ok; i++) + { + uint32_t n, off; + + r = r * 1103515245u + 12345u; + n = 1 + (r >> 16) % 5; + r = r * 1103515245u + 12345u; + off = (r >> 8) % (NSEC - n); + ok = put(off, n, t++) == 0; + } + check("500 more writes, no sync", ok); + check("remount without sync", ftl_init() == 0); + check("... read back", verify() == 0); + check("... consistent", ftl_hook_check(msg, sizeof(msg)) == 0 + || (printf(" %s\n", msg), 0)); + + check("no page programmed twice", mock_reprogram == 0); + check("no page programmed out of order", mock_nonsequential == 0); + check("no two-plane pair the chip could not take", + mock_bad_twoplane == 0); + + /* A bad spare before a replaced block must still mount; superblock + * 1500 holds none of the data. */ + check("a bad spare then a remap commits", + ftl_hook_bad_spare_then_remap(0, 1500) == 0); + check("... and still mounts", ftl_init() == 0); + check("... and reads back", verify() == 0); + + /* Apple's pending-bad list: the next erase replaces the block */ + { + bool moved = false; + check("a pending block is replaced at its erase", + ftl_hook_pending_erase(1, 0, 1600, &moved) == 0 && moved); + /* now its spare is pending: replacing it must clear the entry */ + moved = false; + check("a pending spare is replaced and leaves the list", + ftl_hook_pending_erase(1, 0, 1600, &moved) == 0 && moved); + check("... and still mounts", ftl_init() == 0); + check("... and reads back", verify() == 0); + } + check("VFL commits program every page in order", + mock_nonsequential == 0 && mock_reprogram == 0); + + /* The policy that keeps unvalidated chips safe, in this layout: the + * mount reads but never programs or erases */ + { + const struct nand_geometry good = mock_geo; + unsigned long writes, erases; + static uint8_t one[NAND_PAGE_SIZE]; + + mock_geo.validated = false; + check("an unvalidated row mounts", ftl_init() == 0); + check("... read-only", !ftl_hook_writeable()); + check("... and reads", ftl_read(0, 1, one) == 0); + writes = mock_writes; + erases = mock_erases; + memset(one, 0x5a, sizeof(one)); + check("... refuses a write", ftl_write(0, 1, one) != 0); + check("... sync does nothing", ftl_sync() == 0); + check("... no program or erase reached the NAND", + mock_writes == writes && mock_erases == erases); + mock_geo = good; + check("the validated row mounts writable", + ftl_init() == 0 && ftl_hook_writeable()); + } + + puts(fails ? "SOME FAILED" : "ALL PASS"); + return fails != 0; +} diff --git a/utils/ipodnano3g/ftltest/test_ftl.c b/utils/ipodnano3g/ftltest/test_ftl.c new file mode 100644 index 0000000000..0c3aa0389e --- /dev/null +++ b/utils/ipodnano3g/ftltest/test_ftl.c @@ -0,0 +1,215 @@ +/* Host test of ftl-nano3g.c against the raw dump of a 4GB Nano 3G, or any + * medium ftl_hook_open() can present (the dump in another layout, or data + * collected from a contributor's unit). + * + * The oracle uses nothing the FTL decodes (no map, no contexts, no VFL + * tables): for every logical page it takes, among all pages whose spare + * says "user data, this lpn", the newest - highest usn, then the latest + * position in its superblock. Every lpn the FTL resolves must land on the + * oracle's copy (or, where copies tie, on one with identical data). + * + * Known false positive #1: when two logs for the same logical block each + * hold pages the other lacks, _FTLRestore's own tie-break (0x806a19c in + * osos 1.1.3, matched here instruction for instruction) keeps the more + * complete one whole and drops the other entirely, even if the dropped one + * is newer for some of its pages. ftl_read() then disagrees with this + * oracle on exactly those pages - correctly, since that is what Apple's + * own firmware would also resolve to on the same medium. + * + * Known false positive #2: a superblock whose last page reads + * SPARE_DATA_LAST is classified a closed data block from that one page + * alone ("closed blocks -> map" in decode/APPLE-FTL-WRITE.md) and every + * lpn in it is then addressed purely by position, offset == lpn % sbpages, + * with no per-page check. If one physical page in such a block is stale - + * left over from before the block closed, still carrying an older lpn's + * spare - the oracle credits that spare's lpn with the newest copy at the + * position it actually holds data, while the FTL (like Apple's own + * firmware) trusts the position instead: it lands on the stale page for + * the lpn that owns that spare, and finds nothing at the position the + * oracle expected for the lpn that really belongs there. + * + * Both are confined to a handful of pages on one or two logical blocks on + * a real contributor's dump; wholesale disagreement is not either of + * these, and is a bug. + */ +#include +#include +#include +#include +#include "nand-target.h" +#include "ftl-target.h" +#include "mock_nand.h" + +#define BANKS mock_geo.banks +#define BLOCKS mock_geo.blocks +#define PPB mock_geo.pagesperblock +#define PAGE mock_geo.pagesize +#define NPAGES ((size_t)BANKS * BLOCKS * PPB) + +static uint8_t pagebuf[NAND_MAX_PAGE_SIZE], otherbuf[NAND_MAX_PAGE_SIZE]; + +static void read_g(size_t g, uint8_t *buf) +{ + nand_read_page(g / ((size_t)BLOCKS * PPB), g % ((size_t)BLOCKS * PPB), + buf, NULL); +} + +static int fails; + +static void check(const char *what, int ok) +{ + printf("%s %s\n", ok ? "PASS" : "FAIL", what); + if (!ok) + fails++; +} + +static uint32_t meta_rec[NAND_META_WORDS]; + +static uint32_t meta_word(size_t g, int w) +{ + nand_read_page(g / ((size_t)BLOCKS * PPB), g % ((size_t)BLOCKS * PPB), + pagebuf, meta_rec); + return meta_rec[w]; +} + +static int meta_type_g(size_t g) +{ + return (meta_word(g, 2) >> 8) & 0xff; +} + +/* Position of a physical page in its superblock's write order */ +static uint32_t v_of(size_t g) +{ + uint32_t bank = g / ((size_t)BLOCKS * PPB); + uint32_t block = (g / PPB) % BLOCKS; + uint32_t page = g % PPB; + return page * BANKS * mock_geo.planes + + ftl_hook_block_unit(block) * BANKS + bank; +} + +int main(int argc, char **argv) +{ + const char *spec = argc > 1 ? argv[1] : "../n3g-dump"; + static uint8_t buf[16 * NAND_MAX_PAGE_SIZE] __attribute__((aligned(16))); + uint32_t nsec, npages, spp, lpn, bank, page; + unsigned long mount_reads; + bool realdata = strncmp(spec, "collected:", 10) != 0; + int rc; + + if (ftl_hook_open(spec)) + { + puts("cannot open the medium"); + return 2; + } + + rc = ftl_init(); + mount_reads = mock_reads; + printf("ftl_init() = %d after %lu page reads\n", rc, mount_reads); + check("mounts", rc == 0); + if (rc) + return 1; + nsec = ftl_num_sectors(); + spp = PAGE / NAND_PAGE_SIZE; + npages = nsec / spp; + check("capacity is the row's user superblocks", + npages == mock_geo.userblocks / mock_geo.planes * PPB + * mock_geo.planes * BANKS); + + /* Landmarks, where the data is real */ + if (realdata && nsec == 1936u * 1024) + { + rc = ftl_read(0, 1, buf); + check("lpn 0 reads and is an MBR (55aa at 0x1fe)", + rc == 0 && buf[0x1fe] == 0x55 && buf[0x1ff] == 0xaa); + rc = ftl_read(0x7e, 1, buf); + check("lpn 0x7e is the firmware partition banner", + rc == 0 && !memcmp(buf, "{{~~ /-----\\", 13)); + } + /* Where individual lpns physically live changes whenever Apple's + * firmware runs, so beyond these landmarks everything is checked + * against the oracle. */ + + /* The oracle */ + uint64_t *best = malloc(npages * sizeof(uint64_t)); + uint32_t *where = malloc(npages * sizeof(uint32_t)); + uint8_t *tie = calloc(npages, 1); + memset(best, 0, npages * sizeof(uint64_t)); + memset(where, 0xff, npages * sizeof(uint32_t)); + for (size_t g = 0; g < NPAGES; g++) + { + int t = meta_type_g(g); + if (t != 0x40 && t != 0x41) + continue; + uint32_t l = meta_rec[0]; + if (l >= npages) + continue; + uint64_t key = ((uint64_t)meta_rec[1] << 32) | (v_of(g) + 1); + if (key > best[l]) + { + best[l] = key; + where[l] = g; + tie[l] = 0; + } + else if (key == best[l]) + tie[l] = 1; + } + + unsigned long agree = 0, tieok = 0, unwritten = 0, bad = 0, shown = 0; + for (lpn = 0; lpn < npages; lpn++) + { + if (ftl_locate(lpn, &bank, &page)) + { + bad++; + continue; + } + size_t g = (size_t)bank * BLOCKS * PPB + page; + if (where[lpn] == 0xffffffff) + { + /* no copy anywhere: the FTL must point at an erased page */ + if (meta_type_g(g) == 0xff) + unwritten++; + else if (bad++, shown++ < 10) + printf(" lpn %#x: no copy exists, FTL gives %zu (type %#x)\n", + lpn, g, meta_type_g(g)); + } + else if (g == where[lpn]) + agree++; + else if (tie[lpn] && meta_word(g, 0) == lpn + && (memcpy(otherbuf, pagebuf, PAGE), + read_g(where[lpn], pagebuf), + !memcmp(otherbuf, pagebuf, PAGE))) + tieok++; + else if (bad++, shown++ < 10) + printf(" lpn %#x: FTL gives %zu (usn %#x v %u), oracle %u " + "(usn %#x v %u)\n", lpn, g, meta_word(g, 1), v_of(g), + where[lpn], meta_word(where[lpn], 1), v_of(where[lpn])); + } + printf("oracle: %lu agree, %lu on an identical tied copy, " + "%lu unwritten, %lu disagree\n", agree, tieok, unwritten, bad); + check("every lpn resolves to the oracle's newest copy", bad == 0); + + /* Bulk reads through ftl_read, data compared with the oracle's copy */ + unsigned long rerr = 0, derr = 0; + for (lpn = 0; lpn < npages; lpn += 16) + { + uint32_t n = npages - lpn < 16 ? npages - lpn : 16; + if (ftl_read(lpn * spp, n * spp, buf)) + { + rerr++; + continue; + } + for (uint32_t i = 0; i < n; i++) + if (where[lpn + i] != 0xffffffff + && (read_g(where[lpn + i], pagebuf), + memcmp(buf + i * PAGE, pagebuf, PAGE))) + derr++; + } + printf("ftl_read over all %u sectors: %lu failed calls, %lu pages differ\n", + nsec, rerr, derr); + check("ftl_read returns the oracle's data for every sector", + rerr == 0 && derr == 0); + check("read past the end fails", ftl_read(nsec, 1, buf) != 0); + + printf(fails ? "SOME CHECKS FAILED\n" : "ALL PASS\n"); + return fails ? 1 : 0; +} diff --git a/utils/ipodnano3g/ftltest/test_geometry.c b/utils/ipodnano3g/ftltest/test_geometry.c new file mode 100644 index 0000000000..7943b97183 --- /dev/null +++ b/utils/ipodnano3g/ftltest/test_geometry.c @@ -0,0 +1,62 @@ +/* The mount's chip checks: a row that is not validated mounts read-only, + * a row whose reserved-block count does not match the VFL context is + * refused, and so are unknown layouts and page sizes. */ +#include +#include +#include "nand-target.h" +#include "ftl-target.h" +#include "mock_nand.h" + +static int fails; + +static void check(const char *what, int ok) +{ + printf("%s %s\n", ok ? "PASS" : "FAIL", what); + if (!ok) + fails++; +} + +int main(int argc, char **argv) +{ + static uint8_t buf[4 * NAND_PAGE_SIZE], orig[4 * NAND_PAGE_SIZE]; + const struct nand_geometry good = mock_geo; + int rc; + + mock_open(argc > 1 ? argv[1] : "../n3g-dump"); + + mock_geo.validated = false; + check("unvalidated row mounts", ftl_init() == 0); + check("... read-only", !ftl_hook_writeable()); + check("... reads", ftl_read(1000 * 1024, 4, orig) == 0); + memset(buf, 0x5a, sizeof(buf)); + check("... refuses a write", ftl_write(1000 * 1024, 4, buf) != 0); + check("... sync does nothing", ftl_sync() == 0); + check("... no program or erase reached the NAND", + mock_writes == 0 && mock_erases == 0); + check("... data unchanged", ftl_read(1000 * 1024, 4, buf) == 0 + && !memcmp(buf, orig, sizeof(buf))); + mock_geo = good; + + mock_geo.vflspares = 90; + rc = ftl_init(); + printf(" vflspares 90: %d\n", rc); + check("one reserved block too many is refused", rc == -5); + mock_geo.vflspares = 88; + rc = ftl_init(); + printf(" vflspares 88: %d\n", rc); + check("one reserved block too few is refused", rc == -5); + mock_geo = good; + + mock_geo.layout = NAND_LAYOUT_UNKNOWN; + check("an unknown layout is refused", ftl_init() == -4); + mock_geo = good; + mock_geo.pagesize = 8192; + check("an unsupported page size is refused", ftl_init() == -4); + mock_geo = good; + + check("the real row mounts writable", + ftl_init() == 0 && ftl_hook_writeable()); + + puts(fails ? "SOME FAILED" : "ALL PASS"); + return fails != 0; +} diff --git a/utils/ipodnano3g/ftltest/test_mock.c b/utils/ipodnano3g/ftltest/test_mock.c new file mode 100644 index 0000000000..d5e2c36532 --- /dev/null +++ b/utils/ipodnano3g/ftltest/test_mock.c @@ -0,0 +1,141 @@ +/* Tests for the mock NAND itself: erase/program semantics, the violation + * counters, and - most important - that writes never reach the dump files. + * Everything downstream (the FTL write path) rests on these holding. + */ +#include +#include +#include +#include +#include "nand-target.h" +#include "mock_nand.h" + +#define BANKS 4 +#define BLOCKS 4096 +#define PPB 128 + +static int fails; +static const char *dumpdir; + +static void check(const char *what, int ok) +{ + printf("%s %s\n", ok ? "PASS" : "FAIL", what); + if (!ok) + fails++; +} + +static int all_bytes(const void *p, size_t n, uint8_t v) +{ + const uint8_t *b = p; + while (n--) + if (*b++ != v) + return 0; + return 1; +} + +/* Read straight from the file on disk, bypassing the mapping */ +static int file_page(uint32_t bank, uint32_t page, uint8_t *out) +{ + char path[512]; + size_t g = (size_t)bank * BLOCKS * PPB + page; + int fd, ok; + + snprintf(path, sizeof(path), "%s/nand_data_4banks.bin", dumpdir); + fd = open(path, O_RDONLY); + if (fd < 0) + return -1; + ok = pread(fd, out, NAND_PAGE_SIZE, (off_t)g * NAND_PAGE_SIZE) + == NAND_PAGE_SIZE; + close(fd); + return ok ? 0 : -1; +} + +int main(int argc, char **argv) +{ + static uint8_t buf[NAND_PAGE_SIZE], buf2[NAND_PAGE_SIZE]; + static uint8_t orig[NAND_PAGE_SIZE], pattern[NAND_PAGE_SIZE]; + uint32_t meta[NAND_META_WORDS], meta2[NAND_META_WORDS]; + uint32_t bank = 0, block = 0, base; + int rc, i; + + dumpdir = argc > 1 ? argv[1] : "../n3g-dump"; + mock_open(dumpdir); + + /* Find a block whose page 0 is written, to test erase against */ + for (block = 0; block < BLOCKS; block++) + if (nand_read_page(bank, block * PPB, buf, meta) == NAND_ECC_CORRECTED) + break; + check("found a written block to work on", block < BLOCKS); + if (block >= BLOCKS) + return 1; + base = block * PPB; + memcpy(orig, buf, NAND_PAGE_SIZE); + check("a written page reads CORRECTED and matches the dump", + !file_page(bank, base, buf2) && !memcmp(orig, buf2, NAND_PAGE_SIZE)); + + /* Erase */ + mock_reset_stats(); + check("erase returns 0", nand_erase_block(bank, block) == 0); + rc = nand_read_page(bank, base, buf, meta); + check("an erased page reads CLEAN, all 0xff, meta all 0xff", + rc == NAND_ECC_CLEAN && all_bytes(buf, NAND_PAGE_SIZE, 0xff) + && all_bytes(meta, sizeof(meta), 0xff)); + rc = nand_read_page(bank, base + PPB - 1, buf, meta); + check("erase covers the whole block", + rc == NAND_ECC_CLEAN && all_bytes(buf, NAND_PAGE_SIZE, 0xff)); + check("erase counted", mock_erases == 1); + + /* Program */ + for (i = 0; i < NAND_PAGE_SIZE; i++) + pattern[i] = (uint8_t)(i * 7 + 1); + meta[0] = 0x12345678; meta[1] = 0x9abcdef0; meta[2] = 0x0000ff40; + check("program returns 0", + nand_write_page(bank, base, pattern, meta) == 0); + rc = nand_read_page(bank, base, buf, meta2); + check("a programmed page reads back exactly, as CORRECTED", + rc == NAND_ECC_CORRECTED && !memcmp(buf, pattern, NAND_PAGE_SIZE) + && !memcmp(meta2, meta, sizeof(meta))); + check("clean program raises no violations", + mock_reprogram == 0 && mock_nonsequential == 0); + + /* Program only clears bits */ + memset(buf2, 0x0f, NAND_PAGE_SIZE); + check("reprogramming returns 0", + nand_write_page(bank, base, buf2, meta) == 0); + rc = nand_read_page(bank, base, buf, meta2); + for (i = 0; i < NAND_PAGE_SIZE; i++) + if (buf[i] != (uint8_t)(pattern[i] & 0x0f)) + break; + check("programming only clears bits (reads old AND new)", + i == NAND_PAGE_SIZE); + check("reprogramming a written page is counted", mock_reprogram == 1); + + /* Out-of-order programming within a block */ + mock_reset_stats(); + nand_erase_block(bank, block); + nand_write_page(bank, base + 2, pattern, meta); + check("skipping a page within a block is counted", + mock_nonsequential == 1); + mock_reset_stats(); + nand_erase_block(bank, block); + nand_write_page(bank, base, pattern, meta); + nand_write_page(bank, base + 1, pattern, meta); + check("sequential programming raises no violation", + mock_nonsequential == 0 && mock_reprogram == 0); + + /* Bounds */ + check("out-of-range bank fails", + nand_read_page(BANKS, 0, buf, NULL) == -1 + && nand_write_page(BANKS, 0, pattern, meta) == -1 + && nand_erase_block(BANKS, 0) == -1); + check("out-of-range page/block fails", + nand_read_page(0, BLOCKS * PPB, buf, NULL) == -1 + && nand_write_page(0, BLOCKS * PPB, pattern, meta) == -1 + && nand_erase_block(0, BLOCKS) == -1); + + /* The whole point: none of that reached the file */ + check("the dump file on disk is untouched", + !file_page(bank, base, buf2) && !memcmp(orig, buf2, NAND_PAGE_SIZE)); + + printf(fails ? "SOME CHECKS FAILED\n" : "ALL PASS\n"); + return fails ? 1 : 0; +} diff --git a/utils/ipodnano3g/ftltest/test_permute.c b/utils/ipodnano3g/ftltest/test_permute.c new file mode 100644 index 0000000000..f14b34c3c3 --- /dev/null +++ b/utils/ipodnano3g/ftltest/test_permute.c @@ -0,0 +1,805 @@ +/* Tests for mock_open_layout(): the dump presented as a mode 9 ("halves") + * and a mode 4 ("both") chip. Every check compares what the permuted mock + * returns with an untouched read-only mapping of the dump files, through + * block translations written out here again from the layout definitions + * rather than taken from the mock. + * + * Usage: test_permute DUMPDIR + */ +#include +#include +#include +#include +#include +#include +#include +#include "nand-target.h" +#include "mock_nand.h" + +#define DBANKS 4 +#define DBLOCKS 4096 +#define PPB 128 +#define PS 2048 +#define METASZ 16 +#define DPAGES ((size_t)DBANKS * DBLOCKS * PPB) + +#define CXT_SIZE 0x800 +#define CXT_USED 0x1c +#define CXT_REMAP 0x2c +#define CXT_REMAP_N 820 +#define CXT_RING 0x694 +#define CXT_CKSUM 0x7f8 +#define SPARES 89 +#define FREE 0xfff0 +#define BAD 0xffff + +static int fails; +static const uint8_t *raw_data, *raw_meta; +static int both; /* the layout under test */ +static uint32_t nbanks, nblocks, nunits; + +static void check(const char *what, int ok) +{ + printf("%s %s\n", ok ? "PASS" : "FAIL", what); + if (!ok) + fails++; +} + +static const uint8_t *map_ro(const char *dir, const char *name, size_t want) +{ + char path[512]; + struct stat st; + void *p; + int fd; + + snprintf(path, sizeof(path), "%s/%s", dir, name); + fd = open(path, O_RDONLY); + if (fd < 0 || fstat(fd, &st) || (size_t)st.st_size != want) + { + fprintf(stderr, "%s: missing or wrong size\n", path); + exit(2); + } + p = mmap(NULL, want, PROT_READ, MAP_SHARED | MAP_NORESERVE, fd, 0); + if (p == MAP_FAILED) + { + perror("mmap"); + exit(2); + } + close(fd); + return p; +} + +static uint32_t rng = 0x3a3a1234; +static uint32_t rnd(void) +{ + rng ^= rng << 13; + rng ^= rng >> 17; + rng ^= rng << 5; + return rng; +} + +/* ---- The layouts, from their definitions ---- */ + +/* The formulas' blocks 1 and 2048 (halves) or 4096 (both) trade places, so + * that the dump's context block 1 of the primary bank is new block 1 */ +static uint32_t swap_cxt(uint32_t block) +{ + uint32_t other = both ? 4096 : 2048; + + return block == 1 ? other : block == other ? 1 : block; +} + +/* The formulas alone: where the FTL computes a block, as remap entries + * name replaced blocks */ +static void formula_to_dump(uint32_t bank, uint32_t block, uint32_t *db, + uint32_t *dk) +{ + uint32_t u, v, hi, r; + + if (!both) + { + u = block >= 2048; + v = block - u * 2048; + *db = bank; + *dk = 2 * v + u; + } + else + { + hi = block >= 4096; + r = block - hi * 4096; + v = r / 2; + u = (r & 1) + 2 * hi; + *db = 2 * (u % 2) + bank; + *dk = 2 * v + u / 2; + } +} + +static void formula_from_dump(uint32_t db, uint32_t dk, uint32_t *bank, + uint32_t *block) +{ + uint32_t v = dk / 2, plane = dk % 2, u; + + if (!both) + { + *bank = db; + *block = v + plane * 2048; + } + else + { + u = 2 * plane + db / 2; + *bank = db % 2; + *block = 2 * v + (u & 1) + ((u & 2) ? 4096 : 0); + } +} + +/* Where pages physically are: the formulas with the swap */ +static void to_dump(uint32_t bank, uint32_t block, uint32_t *db, uint32_t *dk) +{ + formula_to_dump(bank, swap_cxt(block), db, dk); +} + +static void from_dump(uint32_t db, uint32_t dk, uint32_t *bank, uint32_t *block) +{ + formula_from_dump(db, dk, bank, block); + *block = swap_cxt(*block); +} + +/* ---- Pages and contexts ---- */ + +static uint16_t get16(const uint8_t *p, size_t off) +{ + return p[off] | p[off + 1] << 8; +} + +static void put16(uint8_t *p, size_t off, uint16_t v) +{ + p[off] = v; + p[off + 1] = v >> 8; +} + +static uint32_t get32(const uint8_t *p, size_t off) +{ + return get16(p, off) | (uint32_t)get16(p, off + 2) << 16; +} + +static void put32(uint8_t *p, size_t off, uint32_t v) +{ + put16(p, off, v); + put16(p, off + 2, v >> 16); +} + +static void cxt_sums(const uint8_t *c, uint32_t *sum, uint32_t *x) +{ + size_t off; + + *sum = *x = 0xAABBCCDD; + for (off = 0; off < CXT_CKSUM; off += 4) + { + *sum += get32(c, off); + *x ^= get32(c, off); + } +} + +static int cxt_ok(const uint8_t *c) +{ + uint32_t sum, x; + + cxt_sums(c, &sum, &x); + return sum == get32(c, CXT_CKSUM) && x == get32(c, CXT_CKSUM + 4); +} + +static void cxt_seal(uint8_t *c) +{ + uint32_t sum, x; + + cxt_sums(c, &sum, &x); + put32(c, CXT_CKSUM, sum); + put32(c, CXT_CKSUM + 4, x); +} + +static int is_cxt(const uint32_t *meta, int rc) +{ + return rc > 0 && rc != NAND_ECC_FAILED + && ((meta[2] >> 8) & 0xff) == 0x80 && (meta[2] & 0xff) == 0; +} + +typedef int (*reader)(uint32_t bank, uint32_t page, uint8_t *buf, + uint32_t *meta); + +/* A page of the dump files, as the NAND driver would read it */ +static int raw_read(uint32_t bank, uint32_t page, uint8_t *buf, uint32_t *meta) +{ + size_t g = (size_t)bank * DBLOCKS * PPB + page; + int32_t rc; + + memcpy(&rc, raw_meta + g * METASZ + 12, 4); + if (rc == NAND_ECC_CLEAN) + { + memset(buf, 0xff, PS); + memset(meta, 0xff, 12); + return rc; + } + memcpy(buf, raw_data + g * PS, PS); + memcpy(meta, raw_meta + g * METASZ, 12); + return rc; +} + +static int mock_read(uint32_t bank, uint32_t page, uint8_t *buf, uint32_t *meta) +{ + return nand_read_page(bank, page, buf, meta); +} + +/* ftl_vfl_read_cxt(): the first good copy of a commit, into buf */ +static int read_cxt(reader rd, uint32_t bank, uint32_t block, uint32_t page, + uint8_t *buf, uint32_t *meta) +{ + uint32_t i; + + for (i = page; i < page + 8 && i < PPB; i++) + if (is_cxt(meta, rd(bank, block * PPB + i, buf, meta)) && cxt_ok(buf)) + return 0; + return -1; +} + +/* The first block from 1 below limit with a context at page 0, or -1 */ +static long scan_cxt(reader rd, uint32_t bank, uint32_t limit, uint8_t *buf) +{ + uint32_t meta[3], block; + + for (block = 1; block < limit; block++) + if (!read_cxt(rd, bank, block, 0, buf, meta)) + return block; + return -1; +} + +/* ftl_vfl_open_bank() on a bank of blocks, with the ring taken from the + * first context below block limit, into out. Returns 0 or -1. */ +static int newest_cxt(reader rd, uint32_t bank, uint32_t limit, + uint32_t blocks, uint8_t *out) +{ + static uint8_t buf[PS]; + uint32_t meta[3], ring[4], i, best = 4, bestusn = 0xffffffff, last; + + if (scan_cxt(rd, bank, limit, buf) < 0) + return -1; + for (i = 0; i < 4; i++) + ring[i] = get16(buf, CXT_RING + 2 * i); + for (i = 0; i < 4; i++) + { + if (ring[i] >= blocks || read_cxt(rd, bank, ring[i], 0, buf, meta)) + continue; + if (meta[0] && meta[0] <= bestusn) + { + bestusn = meta[0]; + best = i; + } + } + if (best == 4) + return -1; + last = 0; + for (i = 8; i < PPB; i += 8) + { + if (read_cxt(rd, bank, ring[best], i, buf, meta)) + break; + last = i; + } + if (read_cxt(rd, bank, ring[best], last, buf, meta)) + return -1; + memcpy(out, buf, CXT_SIZE); + return 0; +} + +static uint8_t dump_newest[DBANKS][CXT_SIZE]; + +/* A remap entry: by the formula, free and bad slots kept */ +static uint16_t map_entry(uint32_t db, uint16_t e) +{ + uint32_t bank, block; + + if (e == FREE || e == BAD) + return e; + formula_from_dump(db, e, &bank, &block); + return block; +} + +/* A ring block: where it now sits */ +static uint16_t map_ring(uint32_t db, uint16_t e) +{ + uint32_t bank, block; + + from_dump(db, e, &bank, &block); + return block; +} + +/* The dump bank a new bank's context header comes from: itself in halves; + * in both, whichever of ce and ce + 2 has the higher usn, as the FTL takes + * its control blocks from the highest usn */ +static uint32_t header_bank(uint32_t bank) +{ + if (!both) + return bank; + return get32(dump_newest[bank + 2], 0) > get32(dump_newest[bank], 0) + ? bank + 2 : bank; +} + +/* What a dump context page should read as: halves rewrites each page */ +static void expect_halves(uint32_t db, const uint8_t *src, uint8_t *out) +{ + uint32_t i; + + memcpy(out, src, CXT_SIZE); + for (i = 0; i < 2 * SPARES; i++) + put16(out, CXT_REMAP + 2 * i, + map_entry(db, get16(src, CXT_REMAP + 2 * i))); + for (i = 0; i < 4; i++) + if (get16(src, CXT_RING + 2 * i) < DBLOCKS) + put16(out, CXT_RING + 2 * i, + map_ring(db, get16(src, CXT_RING + 2 * i))); + cxt_seal(out); +} + +/* ... and both writes one merge of its two banks' newest into all of them */ +static void expect_both(uint32_t ce, uint8_t *out) +{ + uint32_t u, i, db; + + memcpy(out, dump_newest[header_bank(ce)], CXT_SIZE); + memset(out + CXT_REMAP, 0, 2 * CXT_REMAP_N); + for (u = 0; u < 4; u++) + { + db = 2 * (u % 2) + ce; + put16(out, CXT_USED + 2 * u, + get16(dump_newest[db], CXT_USED + 2 * (u / 2))); + for (i = 0; i < SPARES; i++) + put16(out, CXT_REMAP + 2 * (u * SPARES + i), + map_entry(db, get16(dump_newest[db], + CXT_REMAP + 2 * ((u / 2) * SPARES + i)))); + } + for (i = 0; i < 4; i++) + { + uint16_t e = get16(dump_newest[ce], CXT_RING + 2 * i); + put16(out, CXT_RING + 2 * i, e < DBLOCKS ? map_ring(ce, e) : e); + } + cxt_seal(out); +} + +/* ---- The tests ---- */ + +static void test_geometry(void) +{ + const struct nand_geometry *g = nand_get_geometry(); + char what[128]; + + snprintf(what, sizeof(what), "%s: geometry and bank count", + both ? "both" : "halves"); + check(what, g->banks == nbanks && g->blocks == nblocks + && g->pagesperblock == PPB && g->planes == nunits + && g->userblocks == (both ? 7744u : 3872u) && g->vflspares == SPARES + && !g->twoplane && g->pagesize == PS + && g->mode == (both ? 4u : 9u) + && g->layout == (both ? NAND_LAYOUT_BOTH : NAND_LAYOUT_HALVES) + && g->validated && nand_get_bank_count() == nbanks); +} + +/* 2: every dump block is some block of the new layout, exactly once */ +static void test_bijection(void) +{ + static uint8_t hits[DBANKS * DBLOCKS]; + uint32_t bank, block, db, dk, db2, dk2, b2, k2; + unsigned long bad = 0, missed = 0, i; + + memset(hits, 0, sizeof(hits)); + for (bank = 0; bank < nbanks; bank++) + for (block = 0; block < nblocks; block++) + { + to_dump(bank, block, &db, &dk); + mock_layout_to_dump(bank, block, &db2, &dk2); + mock_layout_from_dump(db, dk, &b2, &k2); + if (db >= DBANKS || dk >= DBLOCKS || db != db2 || dk != dk2 + || b2 != bank || k2 != block) + { + bad++; + continue; + } + hits[db * DBLOCKS + dk]++; + } + for (i = 0; i < DBANKS * DBLOCKS; i++) + if (hits[i] != 1) + missed++; + printf(" %lu mistranslated, %lu dump blocks not hit once\n", + bad, missed); + check("translation is a bijection matching the layout definition", + !bad && !missed); +} + +/* 1: random pages read the same as the dump's; context pages excepted */ +static void test_random_reads(void) +{ + static uint8_t buf[PS], want[PS]; + uint32_t meta[3], wmeta[3], db, dk, bank, page; + struct nand_read run[8]; + static uint8_t runbuf[8][PS]; + uint32_t runmeta[8][3]; + unsigned long n = 0, bad = 0, written = 0, cxts = 0; + int i, j, rc, wrc; + + for (i = 0; i < 4000; i++) + { + bank = rnd() % nbanks; + page = rnd() % (nblocks * PPB); + to_dump(bank, page / PPB, &db, &dk); + wrc = raw_read(db, dk * PPB + page % PPB, want, wmeta); + if (is_cxt(wmeta, wrc)) + { + cxts++; + continue; + } + rc = nand_read_page(bank, page, buf, meta); + n++; + written += wrc != NAND_ECC_CLEAN; + if (rc != wrc || memcmp(meta, wmeta, sizeof(meta)) + || memcmp(buf, want, PS)) + bad++; + } + /* The same through read runs, pages of different banks mixed */ + for (i = 0; i < 125; i++) + { + for (j = 0; j < 8; j++) + { + run[j].bank = rnd() % nbanks; + run[j].page = rnd() % (nblocks * PPB); + run[j].buf = runbuf[j]; + run[j].meta = runmeta[j]; + } + if (nand_read_pages(run, 8)) + { + bad++; + continue; + } + for (j = 0; j < 8; j++) + { + to_dump(run[j].bank, run[j].page / PPB, &db, &dk); + wrc = raw_read(db, dk * PPB + run[j].page % PPB, want, wmeta); + if (is_cxt(wmeta, wrc)) + { + cxts++; + continue; + } + n++; + written += wrc != NAND_ECC_CLEAN; + if (run[j].ecc != wrc || memcmp(runmeta[j], wmeta, sizeof(wmeta)) + || memcmp(runbuf[j], want, PS)) + bad++; + } + } + printf(" %lu pages compared (%lu written), %lu context pages " + "skipped, %lu differ\n", n, written, cxts, bad); + check("random pages read as the dump's", !bad && written > 100); +} + +/* 1, context pages: every one in the dump, at its new place */ +static void test_cxt_pages(void) +{ + static uint8_t buf[PS], want[PS], expect[CXT_SIZE]; + uint32_t meta[3], wmeta[3], db, dk, bank, block; + unsigned long n = 0, bad = 0, invalid = 0; + size_t g; + int rc, wrc; + + for (g = 0; g < DPAGES; g++) + { + uint32_t w2; + int32_t r; + + memcpy(&w2, raw_meta + g * METASZ + 8, 4); + memcpy(&r, raw_meta + g * METASZ + 12, 4); + if (((w2 >> 8) & 0xff) != 0x80 || (w2 & 0xff) || r == NAND_ECC_CLEAN) + continue; + db = g / ((size_t)DBLOCKS * PPB); + dk = (g / PPB) % DBLOCKS; + wrc = raw_read(db, dk * PPB + g % PPB, want, wmeta); + if (!cxt_ok(want)) + { + invalid++; + continue; + } + from_dump(db, dk, &bank, &block); + rc = nand_read_page(bank, block * PPB + g % PPB, buf, meta); + if (both) + expect_both(bank, expect); + else + expect_halves(db, want, expect); + n++; + if (rc != wrc || memcmp(meta, wmeta, sizeof(meta)) || !cxt_ok(buf) + || memcmp(buf, expect, CXT_SIZE) + || memcmp(buf + CXT_SIZE, want + CXT_SIZE, PS - CXT_SIZE)) + bad++; + } + printf(" %lu context pages, %lu differ from the expected rewrite, " + "%lu with bad checksums left alone\n", n, bad, invalid); + check("context pages read as rewritten, spare and tail unchanged", + n && !bad); +} + +/* 3: each bank's context can be found, and its ring holds contexts */ +static void test_scan(void) +{ + static uint8_t buf[PS], cxt[PS], rawbuf[PS]; + uint32_t meta[3], bank, i, ring, db, dk; + long at; + char what[160]; + int ok, newok, rawok, anyok; + + for (bank = 0; bank < nbanks; bank++) + { + at = scan_cxt(mock_read, bank, 200, cxt); + snprintf(what, sizeof(what), + "bank %u: a context in physical blocks 1..199", bank); + if (at < 0) + { + at = scan_cxt(mock_read, bank, nblocks, cxt); + if (at >= 0) + { + to_dump(bank, at, &db, &dk); + printf(" bank %u: first context at block %ld " + "(dump bank %u block %u)\n", bank, at, db, dk); + } + } + else + printf(" bank %u: first context at block %ld\n", bank, at); + check(what, at >= 0 && at < 200); + if (at < 0) + continue; + + /* The dump's rings list blocks that were never written, so a ring + * block must hold a context exactly when the block its pages come + * from does in the dump, and one must */ + ok = 1; + anyok = 0; + printf(" ring:"); + for (i = 0; i < 4; i++) + { + ring = get16(cxt, CXT_RING + 2 * i); + if (ring >= nblocks) + { + ok = 0; + continue; + } + to_dump(bank, ring, &db, &dk); + newok = !read_cxt(mock_read, bank, ring, 0, buf, meta); + rawok = !read_cxt(raw_read, db, dk, 0, rawbuf, meta); + printf(" %u%s", ring, newok ? "(cxt)" : ""); + ok &= newok == rawok; + anyok |= newok; + } + printf("\n"); + snprintf(what, sizeof(what), + "bank %u: ring blocks hold contexts at page 0 as in the dump", + bank); + check(what, ok && anyok); + } +} + +/* 4: the newest context's tables name the same blocks as the dump's */ +static void test_remap(void) +{ + static uint8_t cxt[CXT_SIZE]; + uint32_t bank, u, i, sb, splane, db, dk, n, o, hb; + char what[128]; + unsigned long entries, bad; + int ok; + + for (bank = 0; bank < nbanks; bank++) + { + snprintf(what, sizeof(what), + "bank %u: newest context's remap tables and ring", bank); + if (newest_cxt(mock_read, bank, nblocks, nblocks, cxt)) + { + check(what, 0); + continue; + } + entries = bad = 0; + hb = header_bank(bank); + ok = cxt_ok(cxt) + && !memcmp(cxt, dump_newest[hb], CXT_USED) + && !memcmp(cxt + CXT_RING + 8, dump_newest[hb] + CXT_RING + 8, + CXT_CKSUM - CXT_RING - 8); + for (u = 0; u < nunits; u++) + { + sb = both ? 2 * (u % 2) + bank : bank; + splane = both ? u / 2 : u; + if (get16(cxt, CXT_USED + 2 * u) + != get16(dump_newest[sb], CXT_USED + 2 * splane)) + ok = 0; + for (i = 0; i < SPARES; i++) + { + o = get16(dump_newest[sb], CXT_REMAP + 2 * (splane * SPARES + i)); + n = get16(cxt, CXT_REMAP + 2 * (u * SPARES + i)); + if (o == FREE || o == BAD) + { + bad += n != o; + continue; + } + entries++; + formula_to_dump(bank, n, &db, &dk); + bad += n >= nblocks || db != sb || dk != o; + } + } + for (i = nunits * SPARES; i < CXT_REMAP_N; i++) + bad += get16(cxt, CXT_REMAP + 2 * i) != 0; + for (i = 0; i < 4; i++) + { + o = get16(dump_newest[bank], CXT_RING + 2 * i); + n = get16(cxt, CXT_RING + 2 * i); + to_dump(bank, n, &db, &dk); + if (o < DBLOCKS ? (db != bank || dk != o) : n != o) + ok = 0; + } + printf(" bank %u: usn %u, control blocks %u %u %u (header from " + "dump bank %u), %lu remapped entries, %lu wrong\n", + bank, get32(cxt, 0), get16(cxt, 4), get16(cxt, 6), + get16(cxt, 8), hb, entries, bad); + check(what, ok && !bad); + } +} + +/* 5: erase, program, read a translated block; its neighbours stay */ +static int block_is_dump(uint32_t bank, uint32_t block) +{ + static uint8_t buf[PS], want[PS]; + uint32_t meta[3], wmeta[3], db, dk, p; + int rc, wrc; + + to_dump(bank, block, &db, &dk); + for (p = 0; p < PPB; p++) + { + wrc = raw_read(db, dk * PPB + p, want, wmeta); + rc = nand_read_page(bank, block * PPB + p, buf, meta); + if (rc != wrc || memcmp(meta, wmeta, sizeof(meta))) + return 0; + if (!is_cxt(wmeta, wrc) && memcmp(buf, want, PS)) + return 0; + } + return 1; +} + +static void test_erase_program(void) +{ + static uint8_t buf[PS], pat[PS]; + uint32_t meta[3], wmeta[3], bank, block, db, dk, p, i, nb[4], nk[4]; + uint32_t written = 0; + int ok, rc; + size_t g; + + /* A written block in the upper half of the new numbering */ + bank = 1; + block = both ? 4101 : 2100; + for (; block + 2 < nblocks; block += 2) + { + to_dump(bank, block, &db, &dk); + written = 0; + for (p = 0; p < PPB; p++) + written += raw_read(db, dk * PPB + p, buf, meta) != NAND_ECC_CLEAN; + if (written == PPB) + break; + } + /* Neighbours in the new numbering, and in the dump's */ + nb[0] = nb[1] = bank; + nk[0] = block - 1; + nk[1] = block + 1; + from_dump(db, dk - 1, &nb[2], &nk[2]); + from_dump(db, dk + 1, &nb[3], &nk[3]); + printf(" bank %u block %u (dump bank %u block %u), neighbours", + bank, block, db, dk); + for (i = 0; i < 4; i++) + printf(" %u:%u", nb[i], nk[i]); + printf("\n"); + + ok = nand_erase_block(bank, block) == 0; + for (p = 0; p < PPB && ok; p++) + ok = nand_read_page(bank, block * PPB + p, buf, meta) == NAND_ECC_CLEAN; + check("erase of a translated block reads back erased", ok); + + ok = 1; + for (p = 0; p < 4; p++) + { + for (i = 0; i < PS; i++) + pat[i] = i * 7 + p; + wmeta[0] = 0x100 + p; + wmeta[1] = 0xffffffff; + wmeta[2] = 0xffff4000; + ok &= nand_write_page(bank, block * PPB + p, pat, wmeta) == 0; + rc = nand_read_page(bank, block * PPB + p, buf, meta); + ok &= rc == NAND_ECC_CORRECTED && !memcmp(buf, pat, PS) + && !memcmp(meta, wmeta, sizeof(meta)); + /* ... and it landed in the dump block the layout says */ + g = ((size_t)db * DBLOCKS + dk) * PPB + p; + ok &= !memcmp(mock_data + g * PS, pat, PS); + } + ok &= !mock_reprogram && !mock_nonsequential; + check("program of a translated block reads back, in its dump block", ok); + + ok = 1; + for (i = 0; i < 4; i++) + ok &= block_is_dump(nb[i], nk[i]); + check("neighbouring blocks, new and dump numbering, undisturbed", ok); + + /* Bad blocks are named in the new numbering */ + mock_clear_bad(); + mock_set_bad(bank, block, MOCK_FAIL_WRITE | MOCK_FAIL_ERASE); + ok = nand_erase_block(bank, block) == NAND_OP_FAILED + && nand_write_page(bank, block * PPB + 4, pat, wmeta) + == NAND_OP_FAILED; + ok &= nand_read_page(bank, block * PPB + 3, buf, meta) + == NAND_ECC_CORRECTED; + mock_clear_bad(); + mock_set_bad(db, dk, MOCK_FAIL_WRITE); + ok &= nand_write_page(bank, block * PPB + 4, pat, wmeta) == 0; + mock_clear_bad(); + check("bad-block injection uses the new numbering", ok); + + /* A torn program lands on the translated page */ + mock_crash_torn = 1; + mock_crash_after = 1; + if (!setjmp(mock_crash_jmp)) + { + nand_write_page(bank, block * PPB + 5, pat, wmeta); + ok = 0; + } + else + ok = nand_read_page(bank, block * PPB + 5, buf, meta) + == NAND_ECC_FAILED + && nand_read_page(bank, block * PPB + 4, buf, meta) + == NAND_ECC_CORRECTED; + mock_crash_torn = 0; + mock_crash_after = 0; + for (i = 0; i < 4; i++) + ok &= block_is_dump(nb[i], nk[i]); + check("crash injection cuts the translated page only", ok); +} + +static void run(const char *dir, const char *layout) +{ + printf("== %s\n", layout); + both = !strcmp(layout, "both"); + nbanks = both ? 2 : 4; + nblocks = both ? 8192 : 4096; + nunits = both ? 4 : 2; + mock_open_layout(dir, layout); + test_geometry(); + test_bijection(); + test_random_reads(); + test_cxt_pages(); + test_scan(); + test_remap(); + test_erase_program(); +} + +int main(int argc, char **argv) +{ + uint32_t b; + + if (argc != 2) + { + fprintf(stderr, "usage: %s DUMPDIR\n", argv[0]); + return 2; + } + raw_data = map_ro(argv[1], "nand_data_4banks.bin", DPAGES * PS); + raw_meta = map_ro(argv[1], "meta_all.bin", DPAGES * METASZ); + for (b = 0; b < DBANKS; b++) + { + if (newest_cxt(raw_read, b, 200, DBLOCKS, dump_newest[b])) + { + fprintf(stderr, "dump bank %u: no VFL context\n", b); + return 2; + } + printf("dump bank %u: newest context usn %u\n", b, + get32(dump_newest[b], 0)); + } + + run(argv[1], "halves"); + run(argv[1], "both"); + + printf("%s\n", fails ? "SOME FAILED" : "ALL PASS"); + return fails ? 1 : 0; +} diff --git a/utils/ipodnano3g/ftltest/test_restore.c b/utils/ipodnano3g/ftltest/test_restore.c new file mode 100644 index 0000000000..3da083d412 --- /dev/null +++ b/utils/ipodnano3g/ftltest/test_restore.c @@ -0,0 +1,149 @@ +/* Closed data blocks the committed map does not know about. + * + * Apple's disk mode writes whole logical blocks - closed superblocks, last + * page type 0x41 - without committing its block map, so after a large copy + * the committed map is older than dozens of them. Apple's own _FTLRestore + * puts every closed block into the map, the highest usn winning, and treats + * only unclosed ones as logs. Found on the device: after a 77 MB copy in + * Apple's disk mode, Rockbox's mount failed with "more than 17 log blocks". + * + * Simulated here by writing whole blocks through our own FTL, with its + * commits, and then rolling the three control superblocks back to their + * earlier contents: the committed map and pool are old again, and the + * blocks written since are closed but unmapped. + */ +#include +#include +#include +#include "nand-target.h" +#include "ftl-target.h" +#include "mock_nand.h" + +#define SBPAGES 1024 +#define PAGES (4096UL * 128) /* per bank, as mock_nand.c lays them out */ +#define METASZ 16 +#define BASE 1500 +#define NBLK 25 + +static int fails; + +static void check(const char *what, int ok) +{ + printf("%s %s\n", ok ? "PASS" : "FAIL", what); + if (!ok) + fails++; +} + +static void fill(uint8_t *buf, uint32_t lblock) +{ + uint32_t i; + + for (i = 0; i < SBPAGES * NAND_PAGE_SIZE; i++) + buf[i] = (uint8_t)(i * 7 + lblock * 13 + 1); +} + +/* Copy every physical page of the control superblocks out or back in */ +static void ctrl_pages(uint16_t *ctrl, uint8_t *data, uint8_t *meta, int save) +{ + uint32_t i, v, bank, page; + size_t g, k = 0; + + for (i = 0; i < 3; i++) + for (v = 0; v < SBPAGES; v++, k++) + { + if (ftl_sb_locate(ctrl[i], v, &bank, &page)) + { + puts("locate failed"); + exit(1); + } + g = bank * PAGES + page; + if (save) + { + memcpy(data + k * NAND_PAGE_SIZE, + mock_data + g * NAND_PAGE_SIZE, NAND_PAGE_SIZE); + memcpy(meta + k * METASZ, mock_meta + g * METASZ, METASZ); + } + else + { + memcpy(mock_data + g * NAND_PAGE_SIZE, + data + k * NAND_PAGE_SIZE, NAND_PAGE_SIZE); + memcpy(mock_meta + g * METASZ, meta + k * METASZ, METASZ); + } + } +} + +int main(int argc, char **argv) +{ + static uint8_t blk[SBPAGES * NAND_PAGE_SIZE], buf[SBPAGES * NAND_PAGE_SIZE]; + static uint8_t cdata[3 * SBPAGES * NAND_PAGE_SIZE], cmeta[3 * SBPAGES * METASZ]; + uint16_t ctrl[3]; + uint32_t k, bad; + int ok = 1, rc; + + mock_open(argc > 1 ? argv[1] : "../n3g-dump"); + if (ftl_init()) { puts("mount failed"); return 1; } + check("settles", ftl_hook_settle() == 0); + /* A write before anything else marks the context dirty (0x4f), as + * Apple's disk mode does; that mark is part of what gets rolled back */ + { + static uint8_t s0[NAND_PAGE_SIZE]; + check("a write marks the context dirty", + ftl_read(0, 1, s0) == 0 && ftl_write(0, 1, s0) == 0); + } + + ftl_hook_ctrl(ctrl); + ctrl_pages(ctrl, cdata, cmeta, 1); + printf(" control superblocks %u %u %u saved\n", ctrl[0], ctrl[1], ctrl[2]); + + for (k = 0; k < NBLK && ok; k++) + { + fill(blk, BASE + k); + ok = ftl_write((BASE + k) * SBPAGES, SBPAGES, blk) == 0; + } + check("25 whole-block writes", ok); + check("committed", ftl_sync() == 0); + + ctrl_pages(ctrl, cdata, cmeta, 0); + puts(" control superblocks rolled back: the committed map predates them"); + + rc = ftl_init(); + printf(" ftl_init() = %d\n", rc); + check("mounts with 25 closed blocks the committed map lacks", rc == 0); + if (rc) + { + printf("SOME CHECKS FAILED\n"); + return 1; + } + + bad = 0; + for (k = 0; k < NBLK; k++) + { + fill(blk, BASE + k); + if (ftl_read((BASE + k) * SBPAGES, SBPAGES, buf) || memcmp(buf, blk, sizeof(blk))) + bad++; + } + printf(" %u of %u blocks read wrong\n", bad, NBLK); + check("every closed block is read", bad == 0); + check("writable", ftl_hook_writeable()); + + /* A write, a commit and a clean remount must keep all of it */ + memset(buf, 0x5a, NAND_PAGE_SIZE); + check("a small write after the mount", ftl_write(BASE * SBPAGES + 3, 1, buf) == 0); + check("sync", ftl_sync() == 0); + check("clean remount", ftl_init() == 0); + bad = 0; + for (k = 0; k < NBLK; k++) + { + fill(blk, BASE + k); + if (k == 0) + memset(blk + 3 * NAND_PAGE_SIZE, 0x5a, NAND_PAGE_SIZE); + if (ftl_read((BASE + k) * SBPAGES, SBPAGES, buf) || memcmp(buf, blk, sizeof(blk))) + bad++; + } + printf(" %u of %u blocks read wrong after the commit\n", bad, NBLK); + check("all of it survives a commit and remount", bad == 0); + check("NAND rules kept", mock_reprogram == 0 && mock_nonsequential == 0); + + printf(fails ? "SOME CHECKS FAILED\n" : "ALL PASS\n"); + return fails ? 1 : 0; +} diff --git a/utils/ipodnano3g/ftltest/test_wear.c b/utils/ipodnano3g/ftltest/test_wear.c new file mode 100644 index 0000000000..1e908eeee6 --- /dev/null +++ b/utils/ipodnano3g/ftltest/test_wear.c @@ -0,0 +1,157 @@ +/* Wear behaviour of the free pool: allocation must take the least-worn + * block, as Apple's _GetFreeVb does, and repeated writes must spread over + * the pool rather than grinding one block down. */ +#include +#include +#include +#include "nand-target.h" +#include "ftl-target.h" +#include "mock_nand.h" + +#define SBPAGES 1024 +#define POOL 20 + +static int fails; + +static void check(const char *what, int ok) +{ + printf("%s %s\n", ok ? "PASS" : "FAIL", what); + if (!ok) + fails++; +} + +int main(int argc, char **argv) +{ + static uint8_t buf[NAND_PAGE_SIZE]; + uint16_t sb[POOL], ec[POOL]; + uint32_t n, i, bank, page, got, lowest, lowec, spread_before, spread_after; + uint32_t mn, mx, round, poolmax_initial; + + mock_open(argc > 1 ? argv[1] : "../n3g-dump"); + if (ftl_init()) { puts("mount failed"); return 1; } + check("mounts writeable", ftl_hook_writeable()); + /* settle: fold inherited logs so the pool is whole */ + check("initial sync", ftl_hook_settle() == 0); + + n = ftl_pool_snapshot(sb, ec, POOL); + check("the pool is full after a sync", n == POOL); + if (n != POOL) + return 1; + mn = 0xffff; mx = 0; + lowest = sb[0]; lowec = ec[0]; + for (i = 0; i < n; i++) + { + if (ec[i] < mn) mn = ec[i]; + if (ec[i] > mx) mx = ec[i]; + if (ec[i] < lowec) { lowec = ec[i]; lowest = sb[i]; } + } + spread_before = mx - mn; + poolmax_initial = mx; + printf(" pool erase counts: min %u max %u spread %u; least worn is sb %u\n", + mn, mx, spread_before, lowest); + check("the pool is not uniformly worn (so the choice is observable)", + spread_before > 0); + + /* One write: the block it lands in must be the least worn in the pool */ + memset(buf, 0xa5, sizeof(buf)); + check("write", ftl_write(700 * SBPAGES + 1, 1, buf) == 0); + check("locate", ftl_locate(700 * SBPAGES + 1, &bank, &page) == 0); + /* Is that physical page inside the least-worn superblock? A small write + * now lands in a log block, at whatever offset the append cursor was + * on, so look for the page anywhere in the block rather than at its + * index within the logical block. */ + got = 0xffffffff; + for (i = 0; i < SBPAGES; i++) + { + uint32_t b2, p2; + + if (!ftl_sb_locate(lowest, i, &b2, &p2) && b2 == bank && p2 == page) + { + got = lowest; + break; + } + } + printf(" the write landed in sb %u (least worn was %u, ec %u)\n", + got == 0xffffffff ? 0xffffffff : lowest, lowest, lowec); + check("allocation took the least-worn block", got == lowest); + + /* Hammer one logical block and watch the pool even out rather than + * one block taking every cycle */ + for (round = 0; round < 60; round++) + { + memset(buf, (uint8_t)round, sizeof(buf)); + if (ftl_write(700 * SBPAGES + 1, 1, buf) || ftl_sync()) + { + check("60 write+sync rounds", 0); + return 1; + } + } + check("60 write+sync rounds", ftl_init() == 0); + n = ftl_pool_snapshot(sb, ec, POOL); + mn = 0xffff; mx = 0; + for (i = 0; i < n; i++) + { + if (ec[i] < mn) mn = ec[i]; + if (ec[i] > mx) mx = ec[i]; + } + spread_after = mx - mn; + printf(" after 60 rounds: min %u max %u spread %u (was %u)\n", + mn, mx, spread_after, spread_before); + /* Apple's cadence (one move per 20 merges' budget) lets cold blocks + * into the pool, which widens the pool's spread; reported, not judged */ + printf(" pool spread %s\n", spread_after <= spread_before + ? "did not grow" : "grew as cold blocks joined the pool"); + check("the data still reads back", + ftl_read(700 * SBPAGES + 1, 1, buf) == 0 && buf[0] == 59); + + /* Static wear levelling. The signal is not the whole-device spread - + * 1230 of the 1959 superblocks sit near the minimum, so one relocation + * per commit cannot move it in any reasonable number of rounds, and the + * device maximum never falls at all once that block holds cold data. + * The signal is the *pool*: worn blocks should be retired into cold-data + * duty and cold blocks recruited in their place, until the spread is + * under the threshold - at which point levelling must stop, so the cost + * stays bounded. */ + { + uint32_t gmin, gmax, r, poolmax_before, poolmax_after; + uint16_t s2[POOL], e2[POOL]; + + ftl_global_spread(&gmin, &gmax); + printf(" whole-device erase counts: min %u max %u\n", gmin, gmax); + poolmax_before = poolmax_initial; /* before any levelling ran */ + + for (r = 0; r < 120; r++) + { + memset(buf, (uint8_t)r, sizeof(buf)); + if (ftl_write(300 * SBPAGES + 5, 1, buf) || ftl_sync()) + { + check("120 more write+sync rounds", 0); + return 1; + } + } + check("120 more write+sync rounds", ftl_init() == 0); + + n = ftl_pool_snapshot(s2, e2, POOL); + mn = 0xffff; poolmax_after = 0; + for (i = 0; i < n; i++) + { + if (e2[i] < mn) mn = e2[i]; + if (e2[i] > poolmax_after) poolmax_after = e2[i]; + } + printf(" pool after levelling: min %u max %u (started at %u, " + "device max %u)\n", mn, poolmax_after, poolmax_before, gmax); + check("the pool is still whole", n == POOL); + check("worn blocks were retired out of the pool", + poolmax_after < poolmax_before); + check("the pool no longer holds the device's most worn block", + poolmax_after < gmax); + printf(" pool spread after levelling: %u\n", poolmax_after - mn); + check("the data still reads back after levelling", + ftl_read(300 * SBPAGES + 5, 1, buf) == 0 && buf[0] == 119); + check("and the earlier block too", + ftl_read(700 * SBPAGES + 1, 1, buf) == 0 && buf[0] == 59); + } + + printf(fails ? "SOME CHECKS FAILED\n" : "ALL PASS\n"); + return fails ? 1 : 0; +} diff --git a/utils/ipodnano3g/ftltest/test_write.c b/utils/ipodnano3g/ftltest/test_write.c new file mode 100644 index 0000000000..cfc182dad8 --- /dev/null +++ b/utils/ipodnano3g/ftltest/test_write.c @@ -0,0 +1,492 @@ +/* Write-path test for ftl-nano3g.c: write, sync, remount, read back - all + * against a copy-on-write mapping of the 4GB dump, never a device. + * + * Beyond "does the data come back", this checks two things that matter for + * a real iPod: that we never break a NAND rule (no reprogram, no page + * skipped inside a block), and that the image we leave behind still + * satisfies the invariants Apple's own _LoadFTLCxt and _FTLRestore check - + * so its firmware can still mount what we wrote. */ +#include +#include +#include +#include "nand-target.h" +#include "ftl-target.h" +#include "mock_nand.h" + +#define BANKS 4 +#define BLOCKS 4096 +#define PPB 128 +#define PLANES 2 +#define SBPAGES (PPB * PLANES * BANKS) /* 1024 */ +#define NSB ((BLOCKS - PLANES * 89) / PLANES) /* 1959 superblocks */ +#define NPAGES ((size_t)BANKS * BLOCKS * PPB) +#define POOL 20 + +static int fails; + +/* src for the scattered-write test */ +struct scat_ctx { uint32_t lpn[2]; uint8_t *p[2]; }; + +static const void *scat_src(uint32_t lpn, void *ctx) +{ + struct scat_ctx *s = ctx; + int i; + + for (i = 0; i < 2; i++) + if (s->lpn[i] == lpn) + return s->p[i]; + return NULL; +} + +static void check(const char *what, int ok) +{ + printf("%s %s\n", ok ? "PASS" : "FAIL", what); + if (!ok) + fails++; +} + +static uint32_t meta_word(size_t g, int w) +{ + uint32_t v; + memcpy(&v, mock_meta + g * 16 + 4 * w, 4); + return v; +} + +static int meta_type_g(size_t g) +{ + return mock_meta[g * 16 + 9]; +} + +/* (superblock, position within it) -> physical page, through the FTL's own + * mapping so that VFL block remapping is accounted for */ +static size_t g_of(uint32_t sb, uint32_t v) +{ + uint32_t bank, page; + + if (ftl_sb_locate(sb, v, &bank, &page)) + return (size_t)-1; + return (size_t)bank * BLOCKS * PPB + page; +} + +/* 0xff - "erased" - for a page that cannot be located, so a failed mount + * reports failed checks instead of reading out of bounds */ +static int type_at(uint32_t sb, uint32_t v) +{ + size_t g = g_of(sb, v); + + return g == (size_t)-1 ? 0xff : meta_type_g(g); +} + +/* Would Apple's firmware mount this image? Re-derives everything from the + * spare metadata, using none of the FTL's own state. */ +static void check_apple_invariants(uint32_t usersb) +{ + static uint8_t state[NSB]; + uint32_t sb, v, ctrl[3], nctrl = 0, ndata = 0, nfree = 0, nlog = 0; + uint32_t best = NSB, bestv = 0, bestusn = 0xffffffff; + int last_is_cxt; + + for (sb = 0; sb < NSB; sb++) + { + int tlast = type_at(sb, SBPAGES - 1); + int tfirst = type_at(sb, 0); + + if (tlast == 0x41) + state[sb] = 0x41, ndata++; + else if (tlast >= 0x43 && tlast <= 0x4f) + { + state[sb] = 0x42; + if (nctrl < 3) + ctrl[nctrl] = sb; + nctrl++; + } + else if (tlast == 0xff && tfirst == 0xff) + state[sb] = 0x48, nfree++; + else if (tlast == 0xff && tfirst == 0x40) + state[sb] = 0x40, nlog++; + else if (tlast == 0xff && tfirst >= 0x43 && tfirst <= 0x4f) + { + state[sb] = 0x42; + if (nctrl < 3) + ctrl[nctrl] = sb; + nctrl++; + } + else + state[sb] = 0; + } + + check("every superblock classifies as data, control, log or free", + ndata + nctrl + nfree + nlog == NSB); + check("one data superblock per logical block", ndata == usersb); + check("exactly three control superblocks", nctrl == 3); + check("free blocks + logs == 20 (_FTLRestore's invariant)", + nfree + nlog == POOL); + + /* _LoadFTLCxt: of the three control blocks, the one whose page 0 has + * the lowest usn is chosen; scanning it backward past erased pages, + * the first readable page must be the context (0x43). */ + for (v = 0; v < nctrl && v < 3; v++) + { + size_t cg = g_of(ctrl[v], 0); + uint32_t usn = cg == (size_t)-1 ? 0xffffffff : meta_word(cg, 0); + int t = type_at(ctrl[v], 0); + if (t >= 0x43 && t <= 0x4f && usn <= bestusn) + { + bestusn = usn; + best = ctrl[v]; + } + } + check("a control block is selectable by page 0", best != NSB); + if (best == NSB) + return; + last_is_cxt = 0; + for (v = SBPAGES; v-- > 0; ) + { + int t = type_at(best, v); + if (t == 0xff) + continue; + last_is_cxt = (t == 0x43); + bestv = v; + break; + } + check("the newest control block's last written page is the context", + last_is_cxt); + check("the context is not the block's very last page (room to grow)", + bestv < SBPAGES - 1); + (void)state; +} + +/* What Apple's _LoadFTLCxt finds: in the control block whose page 0 has the + * lowest usn, the last written page. A context (0x43) there is trusted; a + * 0x4f mark after it makes Apple run _FTLRestore instead. Returns the page + * type and sets *usn to that page's usn. */ +static int apple_ctrl_tail(uint32_t *usn) +{ + uint32_t sb, v, best = NSB, bestusn = 0xffffffff; + + for (sb = 0; sb < NSB; sb++) + { + int t = type_at(sb, 0); + size_t cg = g_of(sb, 0); + + if (t < 0x43 || t > 0x4f || cg == (size_t)-1) + continue; + if (meta_word(cg, 0) <= bestusn) + { + bestusn = meta_word(cg, 0); + best = sb; + } + } + *usn = 0; + if (best == NSB) + return -1; + for (v = SBPAGES; v-- > 0; ) + { + int t = type_at(best, v); + + if (t == 0xff) + continue; + *usn = meta_word(g_of(best, v), 0); + return t; + } + return -1; +} + +/* src for the dirty-mark test: page idx of every block in [first, first+n) */ +struct every_ctx { uint32_t first, nblk, idx; uint8_t *page; }; + +static const void *every_src(uint32_t lpn, void *ctx) +{ + struct every_ctx *e = ctx; + uint32_t b = lpn / SBPAGES; + + if (lpn % SBPAGES != e->idx || b < e->first || b >= e->first + e->nblk) + return NULL; + return e->page; +} + +int main(int argc, char **argv) +{ + const char *dir = argc > 1 ? argv[1] : "../n3g-dump"; + static uint8_t pattern[8 * NAND_PAGE_SIZE]; + static uint8_t saved[8 * NAND_PAGE_SIZE]; + static uint8_t before[8 * NAND_PAGE_SIZE]; + static uint8_t buf[8 * NAND_PAGE_SIZE]; + uint32_t nsec, usersb, base, n = 6, i; + unsigned long sample_bad = 0; + uint32_t *sample_lpn; + uint8_t *sample_first; + int rc; + + mock_open(dir); + rc = ftl_init(); + check("mounts", rc == 0); + if (rc) + return 1; + check("write state loaded", ftl_hook_writeable()); + nsec = ftl_num_sectors(); + usersb = nsec / SBPAGES; + check("capacity is a whole number of superblocks", nsec % SBPAGES == 0); + + /* A sample of the rest of the medium, to prove writing moved nothing + * it should not have */ + sample_lpn = malloc(2048 * sizeof(uint32_t)); + sample_first = malloc(2048); + for (i = 0; i < 2048; i++) + { + sample_lpn[i] = (uint32_t)(((uint64_t)i * 967) % nsec); + if (ftl_read(sample_lpn[i], 1, buf)) + sample_bad++; + sample_first[i] = buf[0]; + } + check("sampled the medium before writing", sample_bad == 0); + + /* Write across a superblock boundary */ + base = 5 * SBPAGES - 3; + check("saved the region being overwritten", ftl_read(base, n, saved) == 0); + check("saved the region just before it", + ftl_read(base - 4, 4, before) == 0); + for (i = 0; i < n * NAND_PAGE_SIZE; i++) + pattern[i] = (uint8_t)(i * 31 + 7); + + mock_reset_stats(); + check("write returns 0", ftl_write(base, n, pattern) == 0); + check("reads back as written before any sync", + ftl_read(base, n, buf) == 0 + && !memcmp(buf, pattern, n * NAND_PAGE_SIZE)); + check("the sectors just before are untouched", + ftl_read(base - 4, 4, buf) == 0 && !memcmp(buf, before, 4 * NAND_PAGE_SIZE)); + check("sync returns 0", ftl_sync() == 0); + check("no page was reprogrammed", mock_reprogram == 0); + check("no page was skipped within a block", mock_nonsequential == 0); + printf(" %lu reads, %lu writes, %lu erases for 6 sectors + sync\n", + mock_reads, mock_writes, mock_erases); + + /* Remount from what was actually written */ + rc = ftl_init(); + check("remounts after the commit", rc == 0); + check("still writeable after remount", ftl_hook_writeable()); + check("capacity unchanged", ftl_num_sectors() == nsec); + check("the written sectors survive the remount", + ftl_read(base, n, buf) == 0 + && !memcmp(buf, pattern, n * NAND_PAGE_SIZE)); + check("the sectors just before survive too", + ftl_read(base - 4, 4, buf) == 0 + && !memcmp(buf, before, 4 * NAND_PAGE_SIZE)); + + /* Overwrite the same region again, to exercise the committed path */ + mock_reset_stats(); + for (i = 0; i < n * NAND_PAGE_SIZE; i++) + pattern[i] = (uint8_t)(i * 17 + 3); + check("second write returns 0", ftl_write(base, n, pattern) == 0); + check("second sync returns 0", ftl_sync() == 0); + check("second write raises no NAND violations", + mock_reprogram == 0 && mock_nonsequential == 0); + rc = ftl_init(); + check("remounts again", rc == 0); + check("the second write survives", + ftl_read(base, n, buf) == 0 + && !memcmp(buf, pattern, n * NAND_PAGE_SIZE)); + + /* Restoring the original content must give back exactly what was there */ + check("restoring the original content works", + ftl_write(base, n, saved) == 0 && ftl_sync() == 0); + check("the region reads as it originally did", + ftl_read(base, n, buf) == 0 && !memcmp(buf, saved, n * NAND_PAGE_SIZE)); + + /* Nothing else moved */ + sample_bad = 0; + for (i = 0; i < 2048; i++) + { + if (sample_lpn[i] >= base && sample_lpn[i] < base + n) + continue; + if (ftl_read(sample_lpn[i], 1, buf) || buf[0] != sample_first[i]) + sample_bad++; + } + check("the rest of the medium is unchanged", sample_bad == 0); + + /* A write spanning several superblocks */ + { + static uint8_t big[48 * NAND_PAGE_SIZE]; + static uint8_t bigback[48 * NAND_PAGE_SIZE]; + uint32_t bbase = 40 * SBPAGES - 20, bn = 48; /* spans 3 superblocks */ + + for (i = 0; i < bn * NAND_PAGE_SIZE; i++) + big[i] = (uint8_t)(i * 5 + 11); + mock_reset_stats(); + check("a write spanning three superblocks returns 0", + ftl_write(bbase, bn, big) == 0); + check("it raises no NAND violations", + mock_reprogram == 0 && mock_nonsequential == 0); + check("sync after it returns 0", ftl_sync() == 0); + check("it survives a remount", + ftl_init() == 0 && ftl_read(bbase, bn, bigback) == 0 + && !memcmp(bigback, big, bn * NAND_PAGE_SIZE)); + } + + /* Scattered sectors in two logical blocks five apart. Before log blocks + * this cost two whole block copies; now it should cost a couple of page + * appends, and only the eventual sync pays for folding them back. */ + { + static uint8_t sc[2][NAND_PAGE_SIZE]; + uint32_t lo = 60 * SBPAGES + 5, hi = 65 * SBPAGES + 7; + + for (i = 0; i < NAND_PAGE_SIZE; i++) + { + sc[0][i] = (uint8_t)(i * 3 + 1); + sc[1][i] = (uint8_t)(i * 9 + 2); + } + struct scat_ctx sctx = { { lo, hi }, { sc[0], sc[1] } }; + mock_reset_stats(); + check("a scattered write returns 0", + ftl_hook_write_merge(lo, hi, scat_src, &sctx) == 0); + printf(" scattered write of 2 sectors: %lu pages written\n", + mock_writes); + check("it appended pages instead of copying two blocks", + mock_writes < 16); + check("it raises no NAND violations", + mock_reprogram == 0 && mock_nonsequential == 0); + check("both scattered sectors read back", + ftl_read(lo, 1, buf) == 0 && !memcmp(buf, sc[0], NAND_PAGE_SIZE) + && ftl_read(hi, 1, buf) == 0 + && !memcmp(buf, sc[1], NAND_PAGE_SIZE)); + check("they survive a sync and remount", + ftl_sync() == 0 && ftl_init() == 0 + && ftl_read(lo, 1, buf) == 0 && !memcmp(buf, sc[0], NAND_PAGE_SIZE) + && ftl_read(hi, 1, buf) == 0 + && !memcmp(buf, sc[1], NAND_PAGE_SIZE)); + } + + /* An unclean shutdown: write, then remount without syncing. The merged + * blocks are not in the committed map, so the mount has to find them by + * scanning - exactly the case Apple's _FTLRestore exists for. */ + for (i = 0; i < n * NAND_PAGE_SIZE; i++) + pattern[i] = (uint8_t)(i * 13 + 5); + check("write before the simulated power loss", ftl_write(base, n, pattern) == 0); + rc = ftl_init(); + check("mounts after an unclean shutdown", rc == 0); + check("still writeable after an unclean shutdown", ftl_hook_writeable()); + check("the unsynced write is found by the log scan", + ftl_read(base, n, buf) == 0 + && !memcmp(buf, pattern, n * NAND_PAGE_SIZE)); + check("a later sync commits it", + ftl_sync() == 0 && ftl_init() == 0 + && ftl_read(base, n, buf) == 0 + && !memcmp(buf, pattern, n * NAND_PAGE_SIZE)); + + /* Regression: a superblock released by one merge must not be handed + * straight back to the next merge in the same uncommitted window. If it + * is, the committed map still names it for the first logical block while + * it now holds the second one's data, and after an unclean shutdown the + * first block reads the wrong data while the second is unrecoverable. + * Least-worn allocation made this reachable; FIFO only hid it. + * Every block written without a sync must survive the remount. */ + { + static uint8_t blk[12][NAND_PAGE_SIZE]; + uint32_t first = 200, nblk = 12, k, bad = 0; + + for (k = 0; k < nblk; k++) + memset(blk[k], (uint8_t)(0x40 + k), NAND_PAGE_SIZE); + for (k = 0; k < nblk; k++) + if (ftl_write((first + k) * SBPAGES + 9, 1, blk[k])) + bad++; + check("12 single-sector writes to 12 blocks, no sync", bad == 0); + { + uint32_t fc, fr, nl; + + ftl_pool_state(&fc, &fr, &nl); + printf(" %u of them still in uncommitted logs\n", nl); + check("some of them are still uncommitted", nl > 0); + } + check("remounts without a sync", ftl_init() == 0); + bad = 0; + for (k = 0; k < nblk; k++) + if (ftl_read((first + k) * SBPAGES + 9, 1, buf) + || memcmp(buf, blk[k], NAND_PAGE_SIZE)) + bad++; + check("every uncommitted block is recovered by the log scan", + bad == 0); + check("and a sync commits them all", + ftl_sync() == 0 && ftl_init() == 0); + bad = 0; + for (k = 0; k < nblk; k++) + if (ftl_read((first + k) * SBPAGES + 9, 1, buf) + || memcmp(buf, blk[k], NAND_PAGE_SIZE)) + bad++; + check("all 12 survive the commit", bad == 0); + } + + /* More merges than the pool has blocks: ftl_write_merge() must commit + * on its own rather than reuse a block released moments ago. */ + { + static uint8_t big2[NAND_PAGE_SIZE]; + uint32_t first = 400, nblk = 26, k, bad = 0; + + memset(big2, 0x5c, sizeof(big2)); + for (k = 0; k < nblk; k++) + if (ftl_write((first + k) * SBPAGES + 11, 1, big2)) + bad++; + check("26 merges - more than the 20-block pool - succeed", bad == 0); + check("remount after them", ftl_init() == 0); + bad = 0; + for (k = 0; k < nblk; k++) + if (ftl_read((first + k) * SBPAGES + 11, 1, buf) + || memcmp(buf, big2, NAND_PAGE_SIZE)) + bad++; + check("all 26 read back after the automatic commits", bad == 0); + check("final sync", ftl_sync() == 0 && ftl_init() == 0); + } + + /* A sync with nothing written since the last commit must not commit + * again: every shutdown calls it. ftl-nano2g.c returns early on its + * clean flag in the same way. */ + { + unsigned long w; + + check("sync before the idle-sync checks", ftl_sync() == 0); + w = mock_writes; + check("a sync with nothing written returns 0", ftl_sync() == 0); + check("... and programs no pages", mock_writes == w); + check("remount after a clean commit", ftl_init() == 0); + w = mock_writes; + check("a sync straight after remounting programs no pages", + ftl_sync() == 0 && mock_writes == w); + } + + /* One ftl_write_merge() call long enough to commit on its own partway + * through, and then keep writing. The commit leaves a clean context, so + * the dirty mark has to be written again before the later pages, or + * Apple's firmware would trust that context after an unclean stop and + * never look for them. */ + { + static uint8_t pg[NAND_PAGE_SIZE]; + struct every_ctx e = { 600, 20, 7, pg }; + uint32_t usn_before, usn_after, k, bad = 0; + int tail; + + memset(pg, 0x3b, sizeof(pg)); + check("clean before the long write", + ftl_sync() == 0 && apple_ctrl_tail(&usn_before) == 0x43); + check("a long scattered write returns 0", + ftl_hook_write_merge(e.first * SBPAGES, + (e.first + e.nblk) * SBPAGES - 1, + every_src, &e) == 0); + tail = apple_ctrl_tail(&usn_after); + check("it committed on its own partway through", + usn_after != usn_before); + check("pages written after that commit are flagged for Apple (0x4f)", + tail == 0x4f); + check("remount without syncing", ftl_init() == 0); + for (k = 0; k < e.nblk; k++) + if (ftl_read((e.first + k) * SBPAGES + e.idx, 1, buf) + || memcmp(buf, pg, NAND_PAGE_SIZE)) + bad++; + check("every block of the long write reads back", bad == 0); + check("sync after the long write", ftl_sync() == 0 && ftl_init() == 0); + } + + check_apple_invariants(usersb); + + printf(fails ? "SOME CHECKS FAILED\n" : "ALL PASS\n"); + return fails ? 1 : 0; +} diff --git a/utils/ipodnano3g/nandcheck/README.md b/utils/ipodnano3g/nandcheck/README.md new file mode 100644 index 0000000000..69eb899812 --- /dev/null +++ b/utils/ipodnano3g/nandcheck/README.md @@ -0,0 +1,166 @@ +# Nano 3G NAND check + +Apple fitted many different NAND chips to the iPod Nano 3G. Rockbox runs on +all of them, but it only writes to chips that have been validated on +hardware; on the rest the player's storage is read-only. This check +collects what we need to validate your chip. It **changes nothing on your +iPod**: nothing is installed, and nothing is written to its NAND. The image +runs from RAM and is gone when the iPod restarts. + +## Which chip is in your iPod + +Every Nano 3G is model A1236, whichever NAND it has, so the chip has to be +read from the iPod itself: + +1. **Capacity** narrows it down: Settings > About in Apple's firmware + shows 4GB or 8GB, and each row below is one capacity. Both Hynix + parts, the 4GB and the 8GB, are validated. +2. **The check's screen** names it exactly, within seconds and without + changing anything: run steps 1 and 2 below and read the `id` line (the + chip, as in the table) and the `banks` line (how many chip enables + answered). The `verdict` line says whether Rockbox knows that + combination and whether it is validated. + +If your chip is already validated, there is nothing to collect: restart +the iPod. Otherwise please carry on with steps 3 and 4. + +These are all the chips Apple's firmware (1.1.3) supports. Apple picks the +row by the id and the number of chip enables, so one id can appear twice. +Rockbox supports every row, but writes only to validated ones; on the +others the storage is read-only. Apart from the validated chip, that +support has so far been tested only against simulated NAND on a computer, +which is why the archives matter. + +| id | maker | capacity | chip enables | page | status | +|---|---|---|---|---|---| +| `A514D3AD` | Hynix | 4GB | 4 | 2KiB | **validated** | +| `A555D5AD` | Hynix | 8GB | 4 | 2KiB | **validated** | +| `B614D5AD` | Hynix | 8GB | 4 | 4KiB | needed | +| `B614D5EC` | Micronas | 4GB | 2 | 4KiB | checked | +| `B614D5EC` | Micronas | 8GB | 4 | 4KiB | needed | +| `2555D5EC` | Micronas | 8GB | 4 | 2KiB | needed | +| `A585D598` | Toshiba | 4GB | 2 | 2KiB | needed | +| `A585D598` | Toshiba | 8GB | 4 | 2KiB | needed | +| `BA94D598` | Toshiba | 4GB | 2 | 4KiB | needed | +| `BA94D598` | Toshiba | 8GB | 4 | 4KiB | checked | +| `A5D5D589` | Intel | 4GB | 2 | 2KiB | checked | +| `A5D5D589` | Intel | 8GB | 4 | 2KiB | checked | +| `3E94D589` | Intel | 4GB | 2 | 4KiB | checked | +| `3ED5D789` | Intel | 8GB | 2 | 4KiB | needed | +| `A5D5D52C` | Micron | 4GB | 2 | 2KiB | **validated** | +| `A5D5D52C` | Micron | 8GB | 4 | 2KiB | needed | +| `3E94D52C` | Micron | 4GB | 2 | 4KiB | needed | +| `3ED5D72C` | Micron | 8GB | 2 | 4KiB | needed | + +"Needed" means nobody has reported a unit with that chip yet. "Reported" +means someone has, but no check archive has been collected from it. +"Checked" means an archive has, and it mounts and replays correctly in the +host FTL suite - short only of the on-device write test that validation +needs. The id is the first four READ ID bytes read as one little-endian +number: `A514D3AD` is the bytes `AD D3 14 A5`, and the last byte is the +maker (`AD` Hynix, `EC` Micronas, `98` Toshiba, `89` Intel, `2C` Micron). +When a chip is validated, its row in `nand_chip_table[]` +(`firmware/target/arm/s5l8702/ipodnano3g/nand-nano3g.c`) and this table +are updated together. + +## What you need + +- A computer (Linux, macOS or Windows) and a USB cable. +- `mks5lboot`, to send the image to the iPod: build it with + `make -C utils/mks5lboot` (libusb-1.0 development files on Linux). On + Windows, see `utils/mks5lboot/README` for the USB driver it needs. +- Python 3. + +## 1. Get the image + +`nano3g-check.dfu` in this directory is ready to use. Its build shows on +the iPod's screen as "Version:" and in the report as `version`, which is +the commit it was built from. + +To build it yourself instead (an ARM toolchain: `tools/rockboxdev.sh`, or +your distribution's `arm-none-eabi-gcc`): + +``` +utils/ipodnano3g/nandcheck/build.sh check +``` + +This writes `nano3g-check.dfu` in the current directory (and +`nano3g-check-wind3x.dfu` if `wInd3x` is installed). + +### Where the code is + +The image is the Nano 3G bootloader built with `-DNAND_CHECK`: + +| part | file | +|---|---| +| the screen, SysCfg and USB mode | `nand_check()` in `bootloader/ipod-s5l87xx.c` | +| chip identification, the report and the raw USB view | the `NAND_CHECK` block of `firmware/target/arm/s5l8702/ipodnano3g/nand-nano3g.c` | +| Apple's chip table | `nand_chip_table[]` in the same file | +| the read-only mount it reports | `firmware/target/arm/s5l8702/ipodnano3g/ftl-nano3g.c` | +| the build | `build.sh` in this directory | + +`nandcheck.py`'s module comment documents the disk layout the image +serves, and `utils/ipodnano3g/README` describes the host tests that a +collected archive feeds. + +## 2. Run it + +1. Put the iPod in DFU mode +2. Send the image: + ``` + utils/mks5lboot/mks5lboot --dfusend utils/ipodnano3g/nandcheck/nano3g-check.dfu + ``` + (With wInd3x instead: `wInd3x haxdfu`, then + `wInd3x run nano3g-check-wind3x.dfu`, built as above.) +3. Within a few seconds the iPod shows "Nano 3G NAND check" and a short + summary: `battery`, `id` (the chip), `banks`, `row`, `mode`, `pagesize`, + `validated`, `ftl`, `verdict`, `model` and `swvr`. Then "Bootloader USB + mode" once the computer has picked it up. **Photograph the screen.** + +## 3. Collect + +1. Leave USB connected until `collect` finishes: unplugging ends the + iPod's USB session, and the check has to be run again. The iPod appears + as a write-protected disk named "Nano 3G NAND" (4.3 GB on a 4GB unit). + It has no file system: if Windows offers to format it, or macOS says + the disk is not readable, choose Cancel or Ignore. +2. Run, with read access to the disk: + ``` + sudo python3 utils/ipodnano3g/nandcheck/nandcheck.py collect + ``` + It finds the iPod by the report in its first sector. On Windows, run + `python utils\ipodnano3g\nandcheck\nandcheck.py collect` from an + administrator prompt. To name the disk instead, add `/dev/sdX` (Linux), + `/dev/rdiskN` (macOS) or `\\.\PhysicalDriveN` (Windows). It takes + about 7 minutes on a 4GB unit (longer on 8GB) and writes + `nandcheck--x.tar.gz`, a few MB. +3. Unplug USB, then hold MENU+SELECT to restart. The iPod comes back as it + was. + +## 4. Send + +The `.tar.gz` and the photo. The archive holds your chip's structure +records and the unit's model and firmware version - no music, file names or file contents, and not the serial number. +If the verdict says the chip is not in Apple's table, the photo is enough. + +## If something goes wrong + +- **No disk appears.** Check the screen: the summary should be there and + the last line should read "Bootloader USB mode". On Linux, `dmesg` shows + what the computer saw. Send the photo and that output. +- **"no disk carrying the Nano 3G NAND check was found"** with "no + permission to read ...": run as root or administrator. On Linux you can + instead give yourself read access to that disk until it is unplugged: + `sudo setfacl -m u:$USER:r /dev/sdX`. +- **Unplugged too early.** Restart the iPod (MENU+SELECT) and start again + from step 2. + +## What is collected + +| file | contents | +|---|---| +| `report.txt` | chip ids (all eight id bytes, every chip enable), chip table row and derived geometry, read-only mount result, verdict, model, hardware and firmware version, battery voltage | +| `meta.bin` | every page's 12 spare bytes and read result, bank by bank (the host tests' dump format) | +| `results.bin` | every page's full read result: ECC class, most bits corrected in a chunk, erased flag | +| `pages.bin` | every page that is not user data or erased: FTL and VFL control structures, Apple's bad-block records | +| `summary.txt` | page counts by type, ECC failures, timeouts, erased-flag agreement, corrected-bit histogram | \ No newline at end of file diff --git a/utils/ipodnano3g/nandcheck/build.sh b/utils/ipodnano3g/nandcheck/build.sh new file mode 100755 index 0000000000..e8151d45cc --- /dev/null +++ b/utils/ipodnano3g/nandcheck/build.sh @@ -0,0 +1,55 @@ +#!/bin/sh +# Build the iPod Nano 3G NAND check image from this Rockbox tree. +# +# build.sh check [OUTDIR] +# +# The check identifies the chip, tries a read-only mount, then serves the +# raw NAND over USB for nandcheck.py. +# +# Needs an ARM toolchain (Rockbox's from tools/rockboxdev.sh, or +# arm-none-eabi-gcc) and, for mks5lboot, libusb-1.0 development files. +# Writes nano3g-WHAT.dfu, to send with +# utils/mks5lboot/mks5lboot --dfusend nano3g-WHAT.dfu +# from Apple's DFU mode, and nano3g-WHAT-wind3x.dfu if wInd3x is on PATH. +# Nothing is installed on the iPod and nothing is written to its NAND. +set -e + +what=${1:-check} +case "$what" in + check) define=NAND_CHECK ;; + *) echo "usage: $0 check [OUTDIR]" >&2; exit 2 ;; +esac + +root=$(cd "$(dirname "$0")/../../.." && pwd) +mkdir -p "${2:-.}" +out=$(cd "${2:-.}" && pwd) +build="$root/build-nano3g-$what" +jobs=$(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 2) + +# Rockbox's own toolchain if it is on PATH, else a distribution's +# arm-none-eabi one; CROSS=prefix- overrides both +if [ -z "$CROSS" ] && ! command -v arm-elf-eabi-gcc > /dev/null 2>&1 \ + && command -v arm-none-eabi-gcc > /dev/null 2>&1; then + CROSS=arm-none-eabi- +fi + +mkdir -p "$build" +cd "$build" +if [ ! -f Makefile ]; then + "$root/tools/configure" --target=ipodnano3g --type=b \ + ${CROSS:+--compiler-prefix=$CROSS} < /dev/null > configure.log 2>&1 +fi +# The image is the Nano 3G bootloader built with one extra define +if ! grep -q -- "-D$define" Makefile; then + sed "s|^export EXTRA_DEFINES=\(.*\)|export EXTRA_DEFINES=\1 -D$define|" \ + Makefile > Makefile.new && mv Makefile.new Makefile +fi +make -j"$jobs" + +make -C "$root/utils/mks5lboot" > /dev/null +"$root/utils/mks5lboot/mks5lboot" --mkdfu-raw bootloader.bin \ + "$out/nano3g-$what.dfu" +if command -v wInd3x > /dev/null 2>&1; then + wInd3x makedfu -k n3g bootloader.bin "$out/nano3g-$what-wind3x.dfu" +fi +echo "built $out/nano3g-$what.dfu" diff --git a/utils/ipodnano3g/nandcheck/nandcheck.py b/utils/ipodnano3g/nandcheck/nandcheck.py new file mode 100644 index 0000000000..0cbfdc8f3c --- /dev/null +++ b/utils/ipodnano3g/nandcheck/nandcheck.py @@ -0,0 +1,354 @@ +#!/usr/bin/env python3 +r"""Collect what is needed to validate an iPod Nano 3G's NAND for Rockbox. + +1. Build the check image (build.sh check), put the iPod in DFU mode and run + it: mks5lboot --dfusend nano3g-check.dfu + (With wInd3x, an external tool not part of Rockbox: wInd3x haxdfu, then + wInd3x run nano3g-check-wind3x.dfu, built the same way.) + The screen shows the chip, its table row and how a read-only mount went. + Nothing is written to the iPod. +2. Connect USB. The iPod appears as a disk of 2048-byte sectors. +3. Run, with read access to the disk (sudo on Linux/macOS, an + administrator prompt on Windows): + nandcheck.py collect + which finds the disk itself, or name it: /dev/sdX (Linux), /dev/rdiskN + (macOS), \\.\PhysicalDriveN (Windows). + This writes nandcheck--x.tar.gz: the report, the spare + metadata of every page and every page that is not file data - the VFL + and FTL control structures and Apple's bad-block records. No file + contents are included. It takes 10-20 minutes. + Add --full to include every page's data as well (4-8 GB, and it contains + whatever is stored on the iPod). + +The check image serves (nand-check-nano3g.c in the Nano 3G target): + sector 0 text report + 1 + block * banks + bank 128 records of 16 bytes: 3 spare words + and a result word, little endian: -1 on + a timeout, else the ECC class (0 clean, + 1 corrected, 3 failed) | most corrected + bits in a chunk << 8 | 0x10000 erased + metaend + page * secperpage page data, bank by bank + +The archive's meta.bin keeps the dump convention the host tests use (result +0 erased, 1 written, 3 failed, -1 timed out); results.bin has every page's +full result word. + + nandcheck.py collect [DISK|auto] [--out DIR] [--full] + nandcheck.py mkimage DUMPDIR IMAGE (testing: a sparse check-image + view of a 4GB unit's dump) +""" +import argparse +import hashlib +import io +import mmap +import os +import stat +import struct +import sys +import tarfile +import time + +SECTOR = 2048 +RECORD = 16 +MAGIC = "nano3g-nandcheck 1" +DATA_TYPES = (0x40, 0x41) +ERASED = 0xff + + +def is_control(kind): + """Every page but user data and erased ones: FTL control (0x43-0x4f), + VFL contexts (0x80) and whatever else Apple keeps, such as its bad-block + records""" + return kind not in DATA_TYPES and kind != ERASED + + +WINDOWS = sys.platform == "win32" + + +class Disk: + """A disk opened read-only, read in whole sectors""" + + def __init__(self, path): + flags = os.O_RDONLY | getattr(os, "O_BINARY", 0) + direct = False + if not WINDOWS: + try: + if stat.S_ISBLK(os.stat(path).st_mode) and \ + hasattr(os, "O_DIRECT"): + flags |= os.O_DIRECT # no readahead of unwanted sectors + direct = True + except OSError: + pass + self.fd = os.open(path, flags) + self.direct = direct + self.buf = mmap.mmap(-1, SECTOR * 256) # page-aligned for O_DIRECT + + def read(self, sector, count): + out = bytearray() + while count: + n = min(count, 256) + if self.direct: + view = memoryview(self.buf)[:n * SECTOR] + got = os.preadv(self.fd, [view], sector * SECTOR) + data = view[:got] + else: + # Windows raw disks and macOS: seek and read whole sectors + os.lseek(self.fd, sector * SECTOR, os.SEEK_SET) + data = os.read(self.fd, n * SECTOR) + got = len(data) + if got != n * SECTOR: + raise IOError("short read at sector %d" % sector) + out += data + sector += n + count -= n + return bytes(out) + + def close(self): + os.close(self.fd) + + +def candidate_disks(): + """Whole disks this system might show the iPod as""" + if WINDOWS: + return [r"\\.\PhysicalDrive%d" % i for i in range(32)] + if sys.platform == "darwin": + return ["/dev/rdisk%d" % i for i in range(32)] + return sorted("/dev/" + d for d in os.listdir("/sys/block") + if d.startswith(("sd", "mmcblk", "vd"))) + + +def find_disk(): + """The disk whose sector 0 is the check image's report""" + denied = [] + for path in candidate_disks(): + try: + disk = Disk(path) + except PermissionError: + denied.append(path) + continue + except OSError: + continue + try: + first = disk.read(0, 1) + except (OSError, IOError): + first = b"" + disk.close() + if first.startswith(MAGIC.encode()): + return path + hint = "" + if denied: + hint = ("\nno permission to read %s - run as administrator/root, " + "or grant read access to the disk" % ", ".join(denied)) + sys.exit("no disk carrying the Nano 3G NAND check was found" + hint) + + +def parse_report(sector0): + text = sector0.split(b"\0", 1)[0].decode("ascii", "replace") + lines = text.splitlines() + if not lines or lines[0] != MAGIC: + sys.exit("not the Nano 3G NAND check image (sector 0 is %r)" + % lines[:1]) + rep = {} + for line in lines[1:]: + key, _, value = line.partition(" ") + rep[key] = value + return text, rep + + +def ftl_meaning(rc): + """What ftl_init()'s result says about the mount""" + try: + rc = int(rc) + except ValueError: + return "not tried" + fixed = {0: "mounted", -1: "no geometry", + -2: "geometry beyond the FTL's limits", + -3: "no VFL context found", + -4: "layout or page size not supported", + -5: "VFL context does not fit the chip table row"} + if rc in fixed: + return fixed[rc] + if -20 < rc <= -10: + return "no FTL context in the control blocks" + if rc <= -20: + return "FTL context or restore inconsistent" + return "unknown" + + +def progress(done, total, start, what): + rate = done / max(time.time() - start, 1e-3) + eta = (total - done) / rate if rate else 0 + sys.stderr.write("\r%s %5.1f%% %d/%d eta %dm%02ds " % ( + what, 100.0 * done / total, done, total, eta // 60, eta % 60)) + + +def collect(args): + # before the minutes of reading, not after + os.makedirs(args.out, exist_ok=True) + path = find_disk() if args.disk == "auto" else args.disk + print("reading %s" % path) + disk = Disk(path) + text, rep = parse_report(disk.read(0, 1)) + print(text.rstrip()) + ident = rep.get("ids", "unknown").split()[0] + name = "nandcheck-%s-x%s" % (ident, rep.get("banks", "0")) + out = os.path.join(args.out, name + ".tar.gz") + files = {"report.txt": text.encode()} + + if rep.get("row", "none") == "none": + print("\nThe chip is not in Apple's table; only the report is saved.") + else: + banks, blocks = int(rep["banks"]), int(rep["blocks"]) + ppb, pagesize = int(rep["ppb"]), int(rep["pagesize"]) + spp = pagesize // SECTOR + metaend = 1 + blocks * banks + ftl = rep.get("ftl", "?") + print("\nread-only mount: %s (%s)" % (ftl, ftl_meaning(ftl))) + + # Spare records of every page, stored bank by bank like a dump + meta = bytearray(banks * blocks * ppb * RECORD) + results = bytearray(banks * blocks * ppb * 4) + start = time.time() + step = 8 * banks + for s in range(1, metaend, step): + n = min(step, metaend - s) + data = disk.read(s, n) + for i in range(n): + block, bank = divmod(s - 1 + i, banks) + off = (bank * blocks + block) * ppb * RECORD + meta[off:off + ppb * RECORD] = data[i * SECTOR: + (i + 1) * SECTOR] + progress(s - 1 + n, metaend - 1, start, "spare records") + sys.stderr.write("\n") + + # The control pages, and a summary of what the medium holds + counts = {"data": 0, "erased": 0, "ftl": 0, "vfl": 0, "other": 0, + "ecc-failed": 0, "timeout": 0, + "erased-flag-but-written": 0, "erased-type-no-flag": 0} + corrected = [0] * 9 + control = [] + for g in range(banks * blocks * ppb): + off = g * RECORD + word = struct.unpack_from("> 8) & 0xff, 8)] += 1 + norm = 0 if erased else 3 if failed else 1 + struct.pack_into(" [iram_end] -- disassemble an FMSS sequencer program. + +Apple's FIL runs programs on the S5L8702 flash controller's sequencer. They +live in IRAM (0x2200xxxx = osos file offset 0xxxxx). Instruction set as +decoded in ../decode/APPLE-FTL-READ.md: +8 bytes, word0 = op<<24 | rA<<16 | offset-or-rB, word1 = value. +""" +import struct, sys, os + +OSOS = os.environ.get("NANO3G_OSOS", os.path.join( + os.path.dirname(os.path.abspath(__file__)), "osos-1.1.3.bin")) + +FMC = { + 0x000: "FMCTRL0", 0x004: "FMCTRL1", 0x008: "FMCMD", 0x00c: "FMADDR0", + 0x010: "FMADDR1", 0x014: "FMADDR2", 0x018: "FMADDR3", 0x01c: "FMADDR4", + 0x020: "FMADDR5", 0x024: "FMADDR6", 0x028: "FMADDR7", 0x02c: "FMANUM", + 0x030: "FMDNUM", 0x034: "FMDATAW0", 0x038: "FMDATAW1", 0x03c: "FMDATAW2", + 0x040: "FMDATAW3", 0x044: "FMDATAW4?", 0x048: "FMCSTAT", 0x04c: "FMSYND0", + 0x06c: "FMSYND5", 0x070: "FMSYND6", 0x074: "FMSYND7", 0x078: "FMUNK78", + 0x07c: "FMUNK7C", 0x080: "FMFIFO", 0x80c: "FMTRANS0", 0x810: "FMUNK810", + 0x814: "FMTRANS1", 0x840: "FMTRANSSTAT", +} +EVENTS = {0: "CMDDONE(bit1)", 1: "ADDRDONE(bit2)", 2: "bit20", 3: "TRANSDONE(bit3)", + 4: "FMTRANS", 5: "STATUSREADY"} +ALU = {0x0a: "&", 0x0b: "|", 0x0c: "+", 0x0d: "-", 0x13: "<<", 0x14: ">>"} + + +def reg(off): + if off >= 0xc00: + return "SEQ+0x%03x" % off + return FMC.get(off, "FMC+0x%03x" % off) + + +def dis(start, end): + d = open(OSOS, "rb").read() + pc = start + while pc < end: + off = pc - 0x22000000 + w0, w1 = struct.unpack_from("> 24 + a = (w0 >> 16) & 0xff + b = w0 & 0xff + o = w0 & 0xffff + rel = pc - start + if op == 0x00: + txt = "end" + elif op == 0x01: + txt = "%s = 0x%x" % (reg(o), w1) + elif op == 0x02: + txt = "%s = r%d" % (reg(o), a) + elif op == 0x03: + txt = "r%d = mem32[r%d]" % (a, b) + elif op == 0x04: + txt = "r%d = %s & 0x%x" % (a, reg(o), w1) + elif op == 0x05: + txt = "r%d = 0x%x" % (a, w1) + elif op == 0x06: + txt = "r%d = r%d" % (a, b) + elif op == 0x07: + txt = "wait %s" % EVENTS.get(a, "event %d" % a) + elif op == 0x11: + txt = "mem32[r%d] = r%d" % (b, a) + elif op == 0x18: + txt = "r%d = seq32[r%d]" % (a, b) + elif op == 0x19: + txt = "seq32[r%d] = r%d" % (b, a) + elif op in ALU: + txt = ("r%d = r%d %s 0x%x" % (a, b, ALU[op], w1) if w1 + else "r%d %s= r%d" % (a, ALU[op], b)) + elif op == 0x0e: + txt = "if r%d != 0 goto +0x%x (0x%08x)" % (a, w1, start + w1) + elif op == 0x17: + txt = "if r%d == 0 goto +0x%x (0x%08x)" % (a, w1, start + w1) + else: + txt = "op %02x a%d b%d off 0x%x val 0x%x" % (op, a, b, o, w1) + print("%08x +%04x %08x %08x %s" % (pc, rel, w0, w1, txt)) + pc += 8 + + +if __name__ == "__main__": + s = int(sys.argv[1], 0) + e = int(sys.argv[2], 0) if len(sys.argv) > 2 else s + 0x400 + dis(s, e) diff --git a/utils/ipodnano3g/regdiff/fmss_emu.py b/utils/ipodnano3g/regdiff/fmss_emu.py new file mode 100644 index 0000000000..2d7b9ab89f --- /dev/null +++ b/utils/ipodnano3g/regdiff/fmss_emu.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python3 +"""fmss_emu.py -- run an Apple FMSS sequencer program against a mock flash +controller and print the register trace. + +The mock controller always reports ready and pass, and the ECC encoder +always done, so the trace is the success path. Parameters are set up as +Apple's m1fmssWriteSequentialPages (0x082d430c) sets them for a run of pages. + + fmss_emu.py program2 NPAGES two-plane program 0x2200ac38 (mode 8) + fmss_emu.py program1 NPAGES single-plane program 0x22009e98 +""" +import struct, sys, os +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from fmss_dis import OSOS, reg, EVENTS, ALU + +PROGRAMS = {"read": 0x22009030, "read-corrected": 0x22009030, + "program2": 0x2200ac38, + "program1": 0x22009e98} + + +class Emu: + def __init__(self, start, ecc_count=0): + self.d = open(OSOS, "rb").read() + self.start = start + self.r = [0] * 16 + self.io = {} # FMC and SEQ registers + self.mem = {} # parameter memory + self.trace = [] + self.raw = [] # (offset, value) of every FMC write + self.ecc_count = ecc_count + + def mem32(self, a): + return self.mem.get(a, 0) + + def rd(self, off): + if off == 0x810: # ECC result: bit 0 fail, bits 16..19 count + return self.ecc_count << 16 + if off == 0x07c: # encode/decode busy: always done + return 0 + if off == 0x04c: # FMSYND0: NAND status ready, pass + return 0x40 + if off == 0x048: # FMCSTAT + return 0xffffffff + return self.io.get(off, 0) + + def wr(self, off, v): + self.io[off] = v & 0xffffffff + if off < 0xc00: + self.trace.append("%-10s = 0x%x" % (reg(off), v & 0xffffffff)) + self.raw.append((off, v & 0xffffffff)) + + def run(self, limit=200000): + pc = self.start + steps = 0 + while steps < limit: + steps += 1 + w0, w1 = struct.unpack_from("> 24, (w0 >> 16) & 0xff, w0 & 0xff, w0 & 0xffff + r = self.r + nxt = pc + 8 + if op == 0x00: + return + elif op == 0x01: + self.wr(o, w1) + elif op == 0x02: + self.wr(o, r[a]) + elif op == 0x03: + r[a] = self.mem32(r[b]) + elif op == 0x04: + r[a] = self.rd(o) & w1 + elif op == 0x05: + r[a] = w1 + elif op == 0x06: + r[a] = r[b] + elif op == 0x07: + self.trace.append("wait %s" % EVENTS.get(a, a)) + elif op == 0x11: + self.mem[r[b]] = r[a] & 0xffffffff + elif op == 0x18: + r[a] = self.io.get(r[b], 0) + elif op == 0x19: + self.io[r[b]] = r[a] & 0xffffffff + elif op in ALU: + f = {0x0a: lambda x, y: x & y, 0x0b: lambda x, y: x | y, + 0x0c: lambda x, y: x + y, 0x0d: lambda x, y: x - y, + 0x13: lambda x, y: x << y, 0x14: lambda x, y: x >> y}[op] + r[a] = (f(r[b], w1) if w1 else f(r[a], r[b])) & 0xffffffff + elif op == 0x0e: + if r[a]: + nxt = self.start + w1 + elif op == 0x17: + if not r[a]: + nxt = self.start + w1 + else: + raise ValueError("op %02x at %08x" % (op, pc)) + pc = nxt + raise RuntimeError("step limit") + + +def setup_list(e, pages, base_ctrl0=0, units=1): + """pages: list of (bank, page) in the order the program is given them; + units: 2KiB units per page (1 on 2KiB-page chips, 2 on 4KiB)""" + base = 0x40000000 + pagelist, banklist, bufs, spares = base, base + 0x10000, base + 0x20000, base + 0x30000 + cetab, results = base + 0x40000, base + 0x50000 + for b in range(8): + e.mem[cetab + 4 * b] = b + for i, (bank, page) in enumerate(pages): + e.mem[pagelist + 4 * i] = page + e.mem[banklist + 4 * i] = bank + # One buffer per 2KiB unit, as the FILs build the list (0xD28 units) + for u in range(units): + e.mem[bufs + 4 * (i * units + u)] = (0x08000000 + i * 2048 * units + + u * 2048) + for w in range(3): + e.mem[spares + 4 * (i * 3 + w)] = 0x11110000 | (i << 4) | w + e.io.update({0xd04: 8, 0xd0c: pagelist, 0xd10: banklist, + 0xd14: cetab, 0xd18: len(pages), 0xd1c: spares, 0xd20: bufs, + 0xd24: results, 0xd28: units, 0xd48: base_ctrl0, + 0xd4c: 0, 0xd00: 64}) + + +def setup_write(e, npages, banks=4, planes=2): + """Pages in Apple's bank-major order for two-plane programming: for each + row, bank 0 plane 0, bank 0 plane 1, bank 1 plane 0, ...""" + base = 0x40000000 + pagelist, banklist, bufs, spares = base, base + 0x10000, base + 0x20000, base + 0x30000 + cetab = base + 0x40000 + per_row = banks * planes + for b in range(banks): + e.mem[cetab + 4 * b] = b # bank -> chip enable + for i in range(npages): + row, k = divmod(i, per_row) + bank, plane = divmod(k, planes) + block = 1000 + plane # a plane pair of blocks + e.mem[pagelist + 4 * i] = block * 128 + row + e.mem[banklist + 4 * i] = bank + for c in range(4): + e.mem[bufs + 4 * (i * 4 + c)] = 0x08000000 + i * 2048 + c * 512 + for w in range(3): + e.mem[spares + 4 * (i * 3 + w)] = 0x11110000 | (i << 4) | w + e.io.update({0xd04: per_row, 0xd0c: pagelist, 0xd10: banklist, + 0xd14: cetab, 0xd18: npages, 0xd1c: spares, 0xd20: bufs, + 0xd24: 0, 0xd28: 4, 0xd48: 0, 0xd4c: 0, 0xd00: 128 // 2}) + + +if __name__ == "__main__": + name = sys.argv[1] if len(sys.argv) > 1 else "program2" + n = int(sys.argv[2]) if len(sys.argv) > 2 else 8 + e = Emu(PROGRAMS[name], ecc_count=3 if name == "read-corrected" else 0) + if name.startswith("read"): + setup_list(e, [(i % 4, 1000 + i // 4) for i in range(n)]) + else: + setup_write(e, n) + e.run() + for t in e.trace: + print(t) + if name.startswith("read"): + results = e.io[0xd24] - 4 * n + print("results:", " ".join("0x%x" % e.mem[results + 4 * i] + for i in range(n))) diff --git a/utils/ipodnano3g/regdiff/regdiff.py b/utils/ipodnano3g/regdiff/regdiff.py new file mode 100644 index 0000000000..1016350987 --- /dev/null +++ b/utils/ipodnano3g/regdiff/regdiff.py @@ -0,0 +1,215 @@ +#!/usr/bin/env python3 +"""regdiff.py -- check nand_write_pages() in nand-nano3g.c against Apple's +FMSS write programs, register write for register write. + +The driver's write functions are extracted from the source, every FMC +register access is rewritten into a logging call, and the result is compiled +for the host with a mock controller that is always ready and never fails - +the same mock fmss_emu.py runs Apple's programs against. Both are given the +same run of pages and their register writes are compared. + + regdiff.py all scenarios +""" +import os, re, subprocess, sys, tempfile + +HERE = os.path.dirname(os.path.abspath(__file__)) +ROCKBOX = os.path.abspath(os.path.join(HERE, "..", "..", "..")) +DRIVER = ROCKBOX + "/firmware/target/arm/s5l8702/ipodnano3g/nand-nano3g.c" +HEADER = ROCKBOX + "/firmware/export/s5l87xx.h" +sys.path.insert(0, HERE) +import fmss_emu + +FUNCS = ["nand_set_fmctrl0", "nand_wait_reg", "nand_wait_stat", + "nand_clear_status", "nand_send_cmd", "nand_send_addr_page", + "nand_read_status", "nand_finish", "nand_load_spare", "nand_encode", + "nand_run_status", "nand_write_pages"] + +# The base FMCTRL0 value nand_set_fmctrl0() uses without a chip enable +BASE_CTRL0 = 0x43000 + + +def registers(): + regs = {} + for m in re.finditer(r"#define\s+(FM[A-Z0-9_]+)\s+\(\*\(REG32_PTR_T\)" + r"\(FMC_BASE\s*\+\s*(0x[0-9A-Fa-f]+)\)\)", + open(HEADER).read()): + regs[m.group(1)] = int(m.group(2), 16) + return regs + + +def extract(src, name): + m = re.search(r"^(static\s+)?[a-z].*\b%s\(" % name, src, re.M) + if not m: + raise SystemExit("no function " + name) + i = src.index("{", m.start()) + depth = 0 + for j in range(i, len(src)): + if src[j] == "{": + depth += 1 + elif src[j] == "}": + depth -= 1 + if depth == 0: + return src[m.start():j + 1] + + +def transform(code, regs): + names = sorted(regs, key=len, reverse=True) + alt = "|".join(names) + + def reads(expr): + return re.sub(r"\b(%s)\b" % alt, lambda m: "R(0x%x)" % regs[m.group(1)], + expr) + + # nand_wait_reg takes a register offset on the host + code = code.replace("volatile uint32_t *reg", "uint32_t reg") + code = code.replace("*reg & bit", "R(reg) & bit") + code = code.replace("*reg = bit", "W(reg, bit)") + code = re.sub(r"&(%s)\b" % alt, lambda m: "0x%x" % regs[m.group(1)], code) + code = re.sub(r"\b(%s)\s*(\|=|&=)\s*([^;]+);" % alt, + lambda m: "W(0x%x, R(0x%x) %s (%s));" % ( + regs[m.group(1)], regs[m.group(1)], m.group(2)[0], + reads(m.group(3))), code) + code = re.sub(r"\b(%s)\s*=(?!=)\s*([^;]+);" % alt, + lambda m: "W(0x%x, %s);" % (regs[m.group(1)], + reads(m.group(2))), code) + return reads(code) + + +PRELUDE = r""" +#include +#include +#include +#include +#include +static uint32_t io[0x1000]; +static void W(uint32_t off, uint32_t v) { io[off] = v; printf("%03x=%08x\n", off, v); } +static uint32_t R(uint32_t off) +{ + if (off == 0x048 || off == 0x840) return 0xffffffff; /* every event done */ + if (off == 0x04c) return 0x40; /* ready, pass */ + if (off == 0x07c) return 0; /* encoder done */ + return io[off]; +} +#define USEC_TIMER 0UL +#define TIME_BEFORE(a, b) 1 +#define ARRAYLEN(a) (sizeof(a) / sizeof((a)[0])) +#define FMCTRL0_UNK1 (1 << 11) +#define FMCTRL0_AUTOXFER (1 << 24) +#define FMCTRL1_DOTRANSADDR (1 << 0) +#define FMCSTAT_CMDDONE (1 << 1) +#define FMCSTAT_ADDRDONE (1 << 2) +#define FMCSTAT_TRANSDONE (1 << 3) +#define FMCSTAT_UNK20 (1 << 20) +#define FMCSTAT_STATUSREADY (1 << 23) +#define NAND_PAGE_SIZE 2048 +#define NAND_CHUNK_SIZE 512 +#define NAND_OP_FAILED (-2) +struct nand_write { uint32_t bank, page; const void *buf; const uint32_t *meta; }; +struct { uint16_t blocks, pagesperblock, pagesize; } chip = { 4096, 128 }, + *nand_chip = &chip; +static bool nand_ready = true; +static unsigned int nand_banks = 4; +static void commit_dcache_range(const void *p, int n) { (void)p; (void)n; } +""" + + +def driver_harness(regs): + src = open(DRIVER).read() + defines = "\n".join(l for l in src.splitlines() + if re.match(r"#define\s+(NAND_CMD_|NAND_STATUS_|NAND_TUNK1|" + r"NAND_TWP|NAND_POLL_SPINS|NAND_MAX_BANKS)", l)) + protos = "\n".join(transform(re.sub(r"\)\s*$", ");", + extract(src, f).split("{")[0].strip()), regs) + for f in FUNCS) + body = "\n\n".join(transform(extract(src, f), regs) for f in FUNCS) + main = r""" +int main(int argc, char **argv) +{ + static struct nand_write w[64]; + static uint32_t meta[64][3]; + unsigned int n = 0, i; + int two = argv[1][0] == '2'; + chip.pagesize = atoi(argv[2]); + uint32_t bank, page, fb; + while (scanf("%u %u", &bank, &page) == 2) + { + w[n].bank = bank; w[n].page = page; + w[n].buf = (const void *)(intptr_t)(0x08000000 + n * chip.pagesize); + for (i = 0; i < 3; i++) meta[n][i] = 0x11110000 | (n << 4) | i; + w[n].meta = meta[n]; + n++; + } + return nand_write_pages(w, n, two, &fb) ? 1 : 0; +} +""" + return PRELUDE + defines + "\n" + protos + "\n" + body + main + + +def scenarios(): + def rows(nrows, pairs): + out = [] + for r in range(nrows): + if pairs: + for b in range(4): + out += [(b, 1000 * 128 + r), (b, 1001 * 128 + r)] + else: + for p in range(2): + for b in range(4): + out.append((b, (1000 + p) * 128 + r)) + return out + return [("one page", "1", [(0, 1000 * 128)]), + ("one page bank 2", "1", [(2, 1000 * 128 + 5)]), + ("single-plane row", "1", rows(1, False)), + ("single-plane two rows", "1", rows(2, False)), + ("three pages one bank", "1", [(1, 1000 * 128), (1, 1000 * 128 + 1), + (1, 1000 * 128 + 2)]), + ("two-plane row", "2", rows(1, True)), + ("two-plane two rows", "2", rows(2, True)), + ("two-plane three rows", "2", rows(3, True))] + + +def main(): + regs = registers() + tmp = os.path.join(HERE, "build") + os.makedirs(tmp, exist_ok=True) + c = os.path.join(tmp, "h.c") + exe = os.path.join(tmp, "h") + open(c, "w").write(driver_harness(regs)) + r = subprocess.run(["cc", "-std=gnu99", "-w", "-o", exe, c], + capture_output=True, text=True) + if r.returncode: + print(r.stderr[:3000]) + print("harness source:", c) + return 1 + fails = 0 + for (name, mode, pages), size in [(s, z) for z in (2048, 4096) + for s in scenarios()]: + name = "%s %dKiB" % (name, size // 1024) + e = fmss_emu.Emu(fmss_emu.PROGRAMS["program2" if mode == "2" else "program1"]) + fmss_emu.setup_list(e, pages, BASE_CTRL0 | 0x801, units=size // 2048) + e.run() + apple = ["%03x=%08x" % w for w in e.raw] + inp = "".join("%d %d\n" % p for p in pages) + out = subprocess.run([exe, mode, str(size)], input=inp, capture_output=True, + text=True).stdout.split() + # The driver clears FMCSTAT after every operation (nand_clear_status), + # which Apple's FIL does outside the program; that one trailing write + # is expected + if out[:-1] == apple and out[-1:] == ["048=ffffffff"]: + print("PASS %-30s %d register writes identical, + status clear" + % (name, len(apple))) + continue + fails += 1 + k = next((i for i, (a, b) in enumerate(zip(out, apple)) if a != b), + min(len(out), len(apple))) + print("FAIL %-30s first difference at write %d (ours %d, Apple %d)" + % (name, k, len(out), len(apple))) + for i in range(max(0, k - 6), min(max(len(out), len(apple)), k + 6)): + print(" %4d ours %-14s apple %s" % ( + i, out[i] if i < len(out) else "-", apple[i] if i < len(apple) else "-")) + print("ALL IDENTICAL" if not fails else "%d SCENARIOS DIFFER" % fails) + return 1 if fails else 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/utils/ipodnano3g/regdiff/regdiff_read.py b/utils/ipodnano3g/regdiff/regdiff_read.py new file mode 100644 index 0000000000..6bfc56caf8 --- /dev/null +++ b/utils/ipodnano3g/regdiff/regdiff_read.py @@ -0,0 +1,198 @@ +#!/usr/bin/env python3 +"""Compare nand_read_pages() register writes with Apple's read program.""" +import os +import re +import subprocess +import sys + +HERE = os.path.dirname(os.path.abspath(__file__)) +ROCKBOX = os.path.abspath(os.path.join(HERE, "..", "..", "..")) +DRIVER = ROCKBOX + "/firmware/target/arm/s5l8702/ipodnano3g/nand-nano3g.c" +HEADER = ROCKBOX + "/firmware/export/s5l87xx.h" +sys.path.insert(0, HERE) +import fmss_emu + +FUNCS = ["nand_wait_reg", "nand_wait_stat", "nand_clear_status", + "nand_send_cmd", "nand_send_addr_page", "nand_read_status", + "nand_read_ecc_result", "nand_read_loaded_page", "nand_read_pages"] +BASE_CTRL0 = 0x43000 + + +def registers(): + regs = {} + with open(HEADER) as f: + src = f.read() + pattern = (r"#define\s+(FM[A-Z0-9_]+)\s+\(\*\(REG32_PTR_T\)" + r"\(FMC_BASE\s*\+\s*(0x[0-9A-Fa-f]+)\)\)") + for m in re.finditer(pattern, src): + regs[m.group(1)] = int(m.group(2), 16) + return regs + + +def extract(src, name): + m = re.search(r"^(static\s+)?[a-z].*\b%s\(" % name, src, re.M) + if not m: + raise SystemExit("no function " + name) + start = src.index("{", m.start()) + depth = 0 + for pos in range(start, len(src)): + if src[pos] == "{": + depth += 1 + elif src[pos] == "}": + depth -= 1 + if not depth: + return src[m.start():pos + 1] + + +def transform(code, regs): + alt = "|".join(sorted(regs, key=len, reverse=True)) + + def reads(expr): + return re.sub(r"\b(%s)\b" % alt, + lambda m: "R(0x%x)" % regs[m.group(1)], expr) + + code = code.replace("volatile uint32_t *reg", "uint32_t reg") + code = code.replace("*reg & bit", "R(reg) & bit") + code = code.replace("*reg = bit", "W(reg, bit)") + code = re.sub(r"&(%s)\b" % alt, + lambda m: "0x%x" % regs[m.group(1)], code) + code = re.sub(r"\b(%s)\s*(\|=|&=)\s*([^;]+);" % alt, + lambda m: "W(0x%x, R(0x%x) %s (%s));" % + (regs[m.group(1)], regs[m.group(1)], m.group(2)[0], + reads(m.group(3))), code) + code = re.sub(r"\b(%s)\s*=(?!=)\s*([^;]+);" % alt, + lambda m: "W(0x%x, %s);" % + (regs[m.group(1)], reads(m.group(2))), code) + return reads(code) + + +PRELUDE = r""" +#include +#include +#include +#include +static uint32_t io[0x1000]; +static void W(uint32_t off, uint32_t v) +{ io[off] = v; printf("%03x=%08x\n", off, v); } +static uint32_t R(uint32_t off) +{ + if (off == 0x048 || off == 0x840) return 0xffffffff; + if (off == 0x04c) return 0x40; + if (off == 0x07c || off == 0x810) return 0; + return io[off]; +} +#define USEC_TIMER 0UL +#define TIME_BEFORE(a, b) 1 +#define FMCTRL0_UNK1 (1 << 11) +#define FMCTRL0_AUTOXFER (1 << 24) +#define FMCTRL1_DOTRANSADDR (1 << 0) +#define FMCSTAT_CMDDONE (1 << 1) +#define FMCSTAT_ADDRDONE (1 << 2) +#define FMCSTAT_TRANSDONE (1 << 3) +#define FMCSTAT_UNK20 (1 << 20) +#define FMCSTAT_STATUSREADY (1 << 23) +#define FMCSTAT_UNK27 (1 << 27) +#define NAND_PAGE_SIZE 2048 +#define NAND_UNIT_SIZE 2048 +#define NAND_ECC_CLEAN 0 +#define NAND_ECC_CORRECTED 1 +#define NAND_ECC_FAILED 2 +struct nand_read { uint32_t bank, page; void *buf; uint32_t *meta; int ecc; + uint32_t result; }; +struct { uint16_t blocks, pagesperblock, pagesize; } chip = { 4096, 128 }; +static typeof(chip) *nand_chip = &chip; +static bool nand_ready = true; +static unsigned int nand_banks = 4; +static void commit_discard_dcache_range(const void *p, int n) +{ (void)p; (void)n; } +static void discard_dcache_range(const void *p, int n) +{ (void)p; (void)n; } +""" + + +def harness(regs): + with open(DRIVER) as f: + src = f.read() + defines = "\n".join(line for line in src.splitlines() + if re.match(r"#define\s+(NAND_CMD_|NAND_STATUS_|" + r"NAND_TUNK1|NAND_TWP|NAND_POLL_SPINS)", + line)) + protos = "\n".join(transform(re.sub(r"\)\s*$", ");", + extract(src, fn).split("{")[0].strip()), + regs) for fn in FUNCS) + body = "\n\n".join(transform(extract(src, fn), regs) for fn in FUNCS) + main = r""" +int main(int argc, char **argv) +{ + static struct nand_read r[64]; + static uint32_t meta[64][3]; + unsigned int n = 0; + uint32_t bank, page; + chip.pagesize = atoi(argv[1]); + while (scanf("%u %u", &bank, &page) == 2) + { + r[n].bank = bank; r[n].page = page; + r[n].buf = (void *)(intptr_t)(0x08000000 + n * chip.pagesize); + r[n].meta = meta[n]; + n++; + } + return nand_read_pages(r, n) ? 1 : 0; +} +""" + return PRELUDE + defines + "\n" + protos + "\n" + body + main + + +def scenarios(): + return [("one page", [(0, 128000)]), + ("four banks", [(i, 128000) for i in range(4)]), + ("same bank", [(0, 128000 + i) for i in range(3)]), + ("duplicate split", [(0, 128000), (1, 128000), + (0, 128001), (2, 128000)])] + + +def main(): + regs = registers() + build = os.path.join(HERE, "build-read") + os.makedirs(build, exist_ok=True) + cfile, exe = os.path.join(build, "h.c"), os.path.join(build, "h") + with open(cfile, "w") as f: + f.write(harness(regs)) + cc = subprocess.run(["cc", "-std=gnu99", "-w", "-o", exe, cfile], + capture_output=True, text=True) + if cc.returncode: + print(cc.stderr[:4000]) + return 1 + failures = 0 + for (name, pages), size in [(s, z) for z in (2048, 4096) + for s in scenarios()]: + name = "%s %dKiB" % (name, size // 1024) + emu = fmss_emu.Emu(fmss_emu.PROGRAMS["read"]) + fmss_emu.setup_list(emu, pages, units=size // 2048) + emu.io[0xd4c] = BASE_CTRL0 + emu.run() + apple = ["%03x=%08x" % item for item in emu.raw] + inp = "".join("%d %d\n" % item for item in pages) + ours = subprocess.run([exe, str(size)], input=inp, + capture_output=True, + text=True).stdout.split() + if ours == apple: + print("PASS %-26s %d register writes identical" % + (name, len(apple))) + continue + failures += 1 + pos = next((i for i, pair in enumerate(zip(ours, apple)) + if pair[0] != pair[1]), min(len(ours), len(apple))) + print("FAIL %-26s first difference %d (ours %d, Apple %d)" % + (name, pos, len(ours), len(apple))) + for i in range(max(0, pos - 5), min(max(len(ours), len(apple)), + pos + 8)): + print(" %4d ours %-14s apple %s" % + (i, ours[i] if i < len(ours) else "-", + apple[i] if i < len(apple) else "-")) + print("ALL IDENTICAL" if not failures else + "%d SCENARIOS DIFFER" % failures) + return bool(failures) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/utils/ipodpatcher/ipodpatcher.c b/utils/ipodpatcher/ipodpatcher.c index de9ca5b3f7..941055294b 100644 --- a/utils/ipodpatcher/ipodpatcher.c +++ b/utils/ipodpatcher/ipodpatcher.c @@ -257,7 +257,7 @@ int read_partinfo(struct ipod_t* ipod, int silent) if ((ipod->pinfo[0].type != 0) || (ipod->pinfo[0].size == 0) || ((ipod->pinfo[1].type != 0xb) && (ipod->pinfo[1].type != 0xc) && (ipod->pinfo[1].type != PARTTYPE_HFS))) { - if (!silent) fprintf(stderr,"[ERR] Partition layout is not an ipod\n"); + if (!silent) fprintf(stderr,"[ERR] Partition layout is not an iPod\n"); return -1; } @@ -650,7 +650,7 @@ int add_new_image(struct ipod_t* ipod, char* imagename, char* filename, int type } if (memcmp(header+4, ipod->modelname,4)!=0) { - fprintf(stderr,"[ERR] Model name in input file (%c%c%c%c) doesn't match ipod model (%s)\n", + fprintf(stderr,"[ERR] Model name in input file (%c%c%c%c) doesn't match iPod model (%s)\n", header[4],header[5],header[6],header[7], ipod->modelname); close(infile); return -1; @@ -936,7 +936,7 @@ int add_bootloader(struct ipod_t* ipod, char* filename, int type) } if (memcmp(header+4, ipod->modelname,4)!=0) { - fprintf(stderr,"[ERR] Model name in input file (%c%c%c%c) doesn't match ipod model (%s)\n", + fprintf(stderr,"[ERR] Model name in input file (%c%c%c%c) doesn't match iPod model (%s)\n", header[4],header[5],header[6],header[7], ipod->modelname); close(infile); return -1; @@ -1233,7 +1233,7 @@ int write_firmware(struct ipod_t* ipod, char* filename, int type) } if (memcmp(header+4, ipod->modelname,4)!=0) { - fprintf(stderr,"[ERR] Model name in input file (%c%c%c%c) doesn't match ipod model (%s)\n", + fprintf(stderr,"[ERR] Model name in input file (%c%c%c%c) doesn't match iPod model (%s)\n", header[4],header[5],header[6],header[7], ipod->modelname); close(infile); return -1; @@ -1810,10 +1810,10 @@ int ipod_scan(struct ipod_t* ipod) } #ifdef __WIN32__ - printf("[INFO] Ipod found - %s (\"%s\") - disk device %d\n", + printf("[INFO] iPod found - %s (\"%s\") - disk device %d\n", ipod->modelstr,ipod->macpod ? "macpod" : "winpod",i); #else - printf("[INFO] Ipod found - %s (\"%s\") - %s\n", + printf("[INFO] iPod found - %s (\"%s\") - %s\n", ipod->modelstr,ipod->macpod ? "macpod" : "winpod",ipod->diskname); #endif n++; @@ -1828,7 +1828,7 @@ int ipod_scan(struct ipod_t* ipod) memcpy(ipod, &ipod_found, sizeof(struct ipod_t)); } else if(n == 0 && denied) { - printf("[ERR] FATAL: Permission denied on %d device(s) and no ipod detected.\n", denied); + printf("[ERR] FATAL: Permission denied on %d device(s) and no iPod detected.\n", denied); #ifdef __WIN32__ printf("[ERR] You need to run this program with administrator priviledges!\n"); #else @@ -1855,7 +1855,7 @@ int write_dos_partition_table(struct ipod_t* ipod) /* Only support 512-byte sectors at the moment */ if ( ipod->sector_size != 512 ) { - fprintf(stderr,"[ERR] Only ipods with 512 bytes per sector are supported.\n"); + fprintf(stderr,"[ERR] Only iPods with 512 bytes per sector are supported.\n"); return -1; } if(ipod->sectorbuf == NULL) { diff --git a/utils/ipodpatcher/main.c b/utils/ipodpatcher/main.c index e207bf782f..f199173fa9 100644 --- a/utils/ipodpatcher/main.c +++ b/utils/ipodpatcher/main.c @@ -61,8 +61,8 @@ void print_macpod_warning(void) { printf("[INFO] ************************************************************************\n"); printf("[INFO] *** WARNING FOR ROCKBOX USERS\n"); - printf("[INFO] *** You must convert this ipod to FAT32 format (aka a \"winpod\")\n"); - printf("[INFO] *** if you want to run Rockbox. Rockbox WILL NOT work on this ipod.\n"); + printf("[INFO] *** You must convert this iPod to FAT32 format (aka a \"winpod\")\n"); + printf("[INFO] *** if you want to run Rockbox. Rockbox WILL NOT work on this iPod.\n"); printf("[INFO] *** See http://www.rockbox.org/wiki/IpodConversionToFAT32\n"); printf("[INFO] ************************************************************************\n"); } @@ -103,21 +103,21 @@ void print_usage(void) fprintf(stderr,"The .ipodx extension is used for encrypted images for the 2nd Gen Nano.\n\n"); #ifdef __WIN32__ - fprintf(stderr,"DISKNO is the number (e.g. 2) Windows has assigned to your ipod's hard disk.\n"); + fprintf(stderr,"DISKNO is the number (e.g. 2) Windows has assigned to your iPod's hard disk.\n"); fprintf(stderr,"The first hard disk in your computer (i.e. C:\\) will be disk 0, the next disk\n"); fprintf(stderr,"will be disk 1 etc. ipodpatcher will refuse to access a disk unless it\n"); - fprintf(stderr,"can identify it as being an ipod.\n"); + fprintf(stderr,"can identify it as being an iPod.\n"); fprintf(stderr,"\n"); #else #if defined(linux) || defined (__linux) - fprintf(stderr,"\"device\" is the device node (e.g. /dev/sda) assigned to your ipod.\n"); + fprintf(stderr,"\"device\" is the device node (e.g. /dev/sda) assigned to your iPod.\n"); #elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) - fprintf(stderr,"\"device\" is the device node (e.g. /dev/da1) assigned to your ipod.\n"); + fprintf(stderr,"\"device\" is the device node (e.g. /dev/da1) assigned to your iPod.\n"); #elif defined(__APPLE__) && defined(__MACH__) - fprintf(stderr,"\"device\" is the device node (e.g. /dev/disk1) assigned to your ipod.\n"); + fprintf(stderr,"\"device\" is the device node (e.g. /dev/disk1) assigned to your iPod.\n"); #endif fprintf(stderr,"ipodpatcher will refuse to access a disk unless it can identify it as being\n"); - fprintf(stderr,"an ipod.\n"); + fprintf(stderr,"an iPod.\n"); #endif } @@ -169,7 +169,7 @@ int main(int argc, char* argv[]) if ((argc > 1) && (strcmp(argv[1],"--scan")==0)) { if (ipod_scan(&ipod) == 0) - fprintf(stderr,"[ERR] No ipods found.\n"); + fprintf(stderr,"[ERR] No iPods found.\n"); return IPOD_NOT_FOUND; } @@ -186,10 +186,10 @@ int main(int argc, char* argv[]) /* Autoscan for ipods */ n = ipod_scan(&ipod); if (n==0) { - fprintf(stderr,"[ERR] No ipods found, aborting\n"); - fprintf(stderr,"[ERR] Please connect your ipod and ensure it is in disk mode\n"); + fprintf(stderr,"[ERR] No iPods found, aborting\n"); + fprintf(stderr,"[ERR] Please connect your iPod and ensure it is in disk mode\n"); #if defined(__APPLE__) && defined(__MACH__) - fprintf(stderr,"[ERR] Also ensure that itunes is closed, and that your ipod is not mounted.\n"); + fprintf(stderr,"[ERR] Also ensure that itunes is closed, and that your iPod is not mounted.\n"); #elif !defined(__WIN32__) if (geteuid()!=0) { fprintf(stderr,"[ERR] You may also need to run ipodpatcher as root.\n"); @@ -197,8 +197,8 @@ int main(int argc, char* argv[]) #endif fprintf(stderr,"[ERR] Please refer to the Rockbox manual if you continue to have problems.\n"); } else if (n > 1) { - fprintf(stderr,"[ERR] %d ipods found, aborting\n",n); - fprintf(stderr,"[ERR] Please connect only one ipod and re-run ipodpatcher.\n"); + fprintf(stderr,"[ERR] %d iPods found, aborting\n",n); + fprintf(stderr,"[ERR] Please connect only one iPod and re-run ipodpatcher.\n"); return IPOD_MULTIPLE_DEVICES; } else if (n == 1 && ipod.macpod) { return IPOD_WRONG_TYPE; @@ -396,7 +396,7 @@ int main(int argc, char* argv[]) ipod_get_ramsize(&ipod); } - printf("[INFO] Ipod model: %s ",ipod.modelstr); + printf("[INFO] iPod model: %s ",ipod.modelstr); if (ipod.ramsize > 0) { printf("(%dMB RAM) ",ipod.ramsize); } printf("(\"%s\")\n",ipod.macpod ? "macpod" : "winpod"); @@ -564,7 +564,7 @@ int main(int argc, char* argv[]) } else if (action==FORMAT_PARTITION) { printf("WARNING!!! YOU ARE ABOUT TO USE AN EXPERIMENTAL FEATURE.\n"); printf("ALL DATA ON YOUR IPOD WILL BE ERASED.\n"); - printf("Are you sure you want to format your ipod? (y/n):"); + printf("Are you sure you want to format your iPod? (y/n):"); if (fgets(yesno,4,stdin)) { if (yesno[0]=='y') { @@ -581,11 +581,11 @@ int main(int argc, char* argv[]) } } else if (action==CONVERT_TO_FAT32) { if (!ipod.macpod) { - printf("[ERR] Ipod is already FAT32, aborting\n"); + printf("[ERR] iPod is already FAT32, aborting\n"); } else { printf("WARNING!!! YOU ARE ABOUT TO USE AN EXPERIMENTAL FEATURE.\n"); printf("ALL DATA ON YOUR IPOD WILL BE ERASED.\n"); - printf("Are you sure you want to convert your ipod to FAT32? (y/n):"); + printf("Are you sure you want to convert your iPod to FAT32? (y/n):"); if (fgets(yesno,4,stdin)) { if (yesno[0]=='y') { diff --git a/utils/jztool/Makefile b/utils/jztool/Makefile index 02436d47bc..a8483d1f0e 100644 --- a/utils/jztool/Makefile +++ b/utils/jztool/Makefile @@ -14,8 +14,9 @@ else CFLAGS += -O0 -ggdb endif -LIBSOURCES := src/buffer.c src/context.c src/device_info.c \ - src/identify_file.c src/ucl_unpack.c src/usb.c src/x1000.c +LIBSOURCES := src/boot_file.c src/buffer.c src/context.c src/device_info.c \ + src/identify_file.c src/ucl_unpack.c src/usb.c src/x1000.c \ + src/x1600.c SOURCES := $(LIBSOURCES) jztool.c EXTRADEPS := libucl.a libmicrotar.a diff --git a/utils/jztool/include/jztool.h b/utils/jztool/include/jztool.h index 732e65a0ee..b1b2c0f8b3 100644 --- a/utils/jztool/include/jztool.h +++ b/utils/jztool/include/jztool.h @@ -82,11 +82,13 @@ enum jz_device_type { JZ_DEVICE_SHANLINGQ1, JZ_DEVICE_EROSQ, JZ_DEVICE_SHANLINGM0PRO, + JZ_DEVICE_HIBYR1, JZ_NUM_DEVICES, }; enum jz_cpu_type { JZ_CPU_X1000, + JZ_CPU_X1600, JZ_NUM_CPUS, }; @@ -178,6 +180,7 @@ int jz_usb_get_cpu_info(jz_usbdev* dev, char* buffer, size_t buflen); */ int jz_x1000_boot(jz_usbdev* dev, jz_device_type type, const char* filename); +int jz_x1600_boot(jz_usbdev* dev, jz_device_type type, const char* filename); /****************************************************************************** * Buffer API and other functions diff --git a/utils/jztool/jztool.c b/utils/jztool/jztool.c index e5fbad6b40..8ac6efbaef 100644 --- a/utils/jztool/jztool.c +++ b/utils/jztool/jztool.c @@ -70,10 +70,13 @@ void usage_x1000(void) exit(4); } -int cmdline_x1000(int argc, char** argv) +/* The "load" command. Identical for every Ingenic CPU except for the usage + * text and which jz_*_boot() does the work. */ +int cmdline_load(int argc, char** argv, void (*usage_fn)(void), + int (*boot_fn)(jz_usbdev*, jz_device_type, const char*)) { if(argc < 2 || strcmp(argv[0], "load")) { - usage_x1000(); + usage_fn(); return 2; } @@ -83,7 +86,7 @@ int cmdline_x1000(int argc, char** argv) return 1; } - rc = jz_x1000_boot(usbdev, dev_info->device_type, argv[1]); + rc = boot_fn(usbdev, dev_info->device_type, argv[1]); if(rc < 0) { jz_log(jz, JZ_LOG_ERROR, "Boot failed: %d", rc); return 1; @@ -92,6 +95,41 @@ int cmdline_x1000(int argc, char** argv) return 0; } +void usage_x1600(void) +{ + printf( +"Usage:\n" +" jztool hibyr1 load \n" +"\n" +"The 'load' command boots the Rockbox bootloader in recovery mode over the\n" +"X1600 BootROM's USB protocol. The player must be in USB boot mode.\n" +"\n" +"To put a HiBy R1 in USB boot mode:\n" +"\n" +"1. Hold POWER until the player is fully off.\n" +"2. Unplug the USB cable.\n" +"3. Hold down NEXT (the lower button on the right-hand side).\n" +"4. While still holding NEXT, plug the USB cable in. Do NOT press POWER --\n" +" inserting the cable is what powers the SoC, and boot_sel is sampled at\n" +" that instant, so NEXT has to be held before the cable goes in.\n" +"5. Release NEXT after about three seconds.\n" +"\n" +"The player gives no visible indication. It should appear as USB device\n" +"a108:eaef, \"Ingenic USB BOOT DEVICE\". Prefer a port directly on the\n" +"computer over a hub chain.\n" +"\n" +"If the BootROM stops responding, a full power-off is required -- unplug the\n" +"cable and let the player discharge, then start again from step 1. Replugging\n" +"alone is not enough.\n" +"\n" +"Note for Windows users: you need to install the WinUSB driver using a\n" +"3rd-party tool such as Zadig before this tool\n" +"can access your player in USB boot mode.\n" +"\n"); + + exit(4); +} + void usage(void) { printf("Usage:\n" @@ -203,7 +241,10 @@ int main(int argc, char** argv) case JZ_DEVICE_SHANLINGQ1: case JZ_DEVICE_EROSQ: case JZ_DEVICE_SHANLINGM0PRO: - return cmdline_x1000(argc, argv); + return cmdline_load(argc, argv, usage_x1000, jz_x1000_boot); + + case JZ_DEVICE_HIBYR1: + return cmdline_load(argc, argv, usage_x1600, jz_x1600_boot); default: jz_log(jz, JZ_LOG_ERROR, "INTERNAL ERROR: unhandled device type"); diff --git a/utils/jztool/src/boot_file.c b/utils/jztool/src/boot_file.c new file mode 100644 index 0000000000..4a0273e5fb --- /dev/null +++ b/utils/jztool/src/boot_file.c @@ -0,0 +1,88 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2021 Aidan MacDonald + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +/* Reading members out of a Rockbox boot package. The package layout is the + * same whatever the SoC is, so the per-CPU boot code shares this. */ + +#include "jztool.h" +#include "jztool_private.h" +#include "microtar-stdio.h" +#include + +int jz_boot_get_file(jz_context* jz, struct mtar* tar, const char* file, + unsigned int flags, jz_buffer** buf) +{ + jz_buffer* buffer = NULL; + const mtar_header_t* h; + int rc; + + rc = mtar_find(tar, file); + if(rc != MTAR_ESUCCESS) { + if(!(flags & JZ_BOOT_OPTIONAL)) + jz_log(jz, JZ_LOG_ERROR, "can't find %s in boot file, tar error %d", + file, rc); + return JZ_ERR_OPEN_FILE; + } + + h = mtar_get_header(tar); + buffer = jz_buffer_alloc(h->size, NULL); + if(!buffer) + return JZ_ERR_OUT_OF_MEMORY; + + rc = mtar_read_data(tar, buffer->data, buffer->size); + if(rc < 0 || (unsigned)rc != buffer->size) { + jz_buffer_free(buffer); + jz_log(jz, JZ_LOG_ERROR, "can't read %s in boot file, tar error %d", + file, rc); + return JZ_ERR_BAD_FILE_FORMAT; + } + + if(flags & JZ_BOOT_DECOMPRESS) { + uint32_t dst_len; + jz_buffer* nbuf = jz_ucl_unpack(buffer->data, buffer->size, &dst_len); + jz_buffer_free(buffer); + if(!nbuf) { + jz_log(jz, JZ_LOG_ERROR, "error decompressing %s in boot file", file); + return JZ_ERR_BAD_FILE_FORMAT; + } + + /* for simplicity just forget original size of buffer */ + nbuf->size = dst_len; + buffer = nbuf; + } + + *buf = buffer; + return JZ_SUCCESS; +} + +int jz_boot_show_version(jz_context* jz, jz_buffer* info_file) +{ + /* Extract the version string and log it for informational purposes */ + char* boot_version = (char*)info_file->data; + char* endpos = memchr(boot_version, '\n', info_file->size); + if(!endpos) { + jz_log(jz, JZ_LOG_ERROR, "invalid metadata in boot file"); + return JZ_ERR_BAD_FILE_FORMAT; + } + + *endpos = 0; + jz_log(jz, JZ_LOG_NOTICE, "Rockbox bootloader version: %s", boot_version); + return JZ_SUCCESS; +} diff --git a/utils/jztool/src/device_info.c b/utils/jztool/src/device_info.c index 69cce405cf..77baa4c303 100644 --- a/utils/jztool/src/device_info.c +++ b/utils/jztool/src/device_info.c @@ -58,6 +58,19 @@ static const jz_device_info infotable[JZ_NUM_DEVICES] = { .vendor_id = 0x0525, .product_id = 0xa4a5, }, + [JZ_DEVICE_HIBYR1] = { + .name = "hibyr1", + .file_ext = "r1", + .description = "HiBy R1", + .device_type = JZ_DEVICE_HIBYR1, + .cpu_type = JZ_CPU_X1600, + /* NOTE(x1600): these are the mass-storage-mode IDs and are UNVERIFIED. + * The only IDs confirmed on the device are the BootROM's, which live in + * the cputable entry below. Fill these in from `lsusb` with the player + * booted into its normal firmware and mounted as UMS. */ + .vendor_id = 0x0000, + .product_id = 0x0000, + }, }; static const jz_cpu_info cputable[JZ_NUM_CPUS] = { @@ -70,6 +83,24 @@ static const jz_cpu_info cputable[JZ_NUM_CPUS] = { .stage2_load_addr = 0x80004000, .stage2_exec_addr = 0x80004000, }, + [JZ_CPU_X1600] = { + /* A HiBy R1 in BootROM USB mode enumerates as + * a108:eaef "Ingenic USB BOOT DEVICE", interface class 255, bulk + * endpoints 0x01 OUT / 0x81 IN with 512-byte packets, and answers + * GET_CPU_INFO (with bmRequestType 0xC0, see src/usb.c) with "X1600". */ + .info_str = "X1600", + .vendor_id = 0xa108, + .product_id = 0xeaef, + + /* Raw USB stage1, linked above the BootROM's cache area (PM 34.3). + * Unlike the X1000 flash SPL, this image has no header to skip. */ + .stage1_load_addr = 0x8000a000, + .stage1_exec_addr = 0x8000a000, + + /* Matches X1600_BOOT_LOAD_ADDR in firmware/export/x1600.h. */ + .stage2_load_addr = 0x80100000, + .stage2_exec_addr = 0x80100000, + }, }; /** \brief Lookup info for a device by type, returns NULL if not found. */ diff --git a/utils/jztool/src/jztool_private.h b/utils/jztool/src/jztool_private.h index 8843dc8304..016faf4095 100644 --- a/utils/jztool/src/jztool_private.h +++ b/utils/jztool/src/jztool_private.h @@ -40,4 +40,18 @@ struct jz_usbdev { int jz_context_ref_libusb(jz_context* jz); void jz_context_unref_libusb(jz_context* jz); +/* Boot package access, shared by the per-CPU boot code. Declared as + * "struct mtar" rather than mtar_t so this header need not pull in + * microtar.h; it is the same type microtar typedefs. */ +struct mtar; + +enum { + JZ_BOOT_DECOMPRESS = 0x01, + JZ_BOOT_OPTIONAL = 0x02, +}; + +int jz_boot_get_file(jz_context* jz, struct mtar* tar, const char* file, + unsigned int flags, jz_buffer** buf); +int jz_boot_show_version(jz_context* jz, jz_buffer* info_file); + #endif /* JZTOOL_PRIVATE_H */ diff --git a/utils/jztool/src/usb.c b/utils/jztool/src/usb.c index 7747f3c27e..ac11eb4cd7 100644 --- a/utils/jztool/src/usb.c +++ b/utils/jztool/src/usb.c @@ -143,13 +143,36 @@ void jz_usb_close(jz_usbdev* dev) free(dev); } +// Direction of each vendor request's control transfer. +// +// GET_CPU_INFO returns data, so it must be a device-to-host (IN) request. The +// Ingenic documentation says otherwise -- X1600 PM Table 34-6 lists it as +// "bmRequestType = 40H (D7 0: Host to Device)" *with* wLength = 0008H, which is +// self-contradictory -- and jztool originally followed the documentation and +// hard-coded LIBUSB_ENDPOINT_OUT for every request. +// +// VERIFIED on X1600 hardware (HiBy R1 in BootROM USB mode, a108:eaef): request +// 0 with bmRequestType 0x40 fails with EIO, and with 0xC0 it returns the 5-byte +// string "X1600". Every other request carries no data and stays OUT. +static const unsigned char vendor_req_dir[6] = { + [VR_GET_CPU_INFO] = LIBUSB_ENDPOINT_IN, + [VR_SET_DATA_ADDRESS] = LIBUSB_ENDPOINT_OUT, + [VR_SET_DATA_LENGTH] = LIBUSB_ENDPOINT_OUT, + [VR_FLUSH_CACHES] = LIBUSB_ENDPOINT_OUT, + [VR_PROGRAM_START1] = LIBUSB_ENDPOINT_OUT, + [VR_PROGRAM_START2] = LIBUSB_ENDPOINT_OUT, +}; + // Does an Ingenic-specific vendor request // Written with X1000 in mind but other Ingenic CPUs have the same commands static int jz_usb_vendor_req(jz_usbdev* dev, int req, uint32_t arg, void* buffer, int buflen) { + if(req < 0 || req >= (int)(sizeof(vendor_req_dir)/sizeof(vendor_req_dir[0]))) + return JZ_ERR_OTHER; + int rc = libusb_control_transfer(dev->handle, - LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + vendor_req_dir[req] | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, req, arg >> 16, arg & 0xffff, buffer, buflen, 1000); if(rc < 0) { @@ -276,7 +299,11 @@ int jz_usb_flush_caches(jz_usbdev* dev) */ int jz_usb_get_cpu_info(jz_usbdev* dev, char* buffer, size_t buflen) { + /* Must be zeroed first: the X1000 answers with 8 bytes ("X1000_v1") but the + * X1600 answers with only 5 ("X1600"), and jz_usb_vendor_req() does not + * report the transferred length, so the tail would otherwise be garbage. */ char tmpbuf[JZ_CPUINFO_BUFLEN]; + memset(tmpbuf, 0, sizeof(tmpbuf)); int rc = jz_usb_vendor_req(dev, VR_GET_CPU_INFO, 0, tmpbuf, 8); if(rc != JZ_SUCCESS) return rc; diff --git a/utils/jztool/src/x1000.c b/utils/jztool/src/x1000.c index 7c3b08b54b..368e405405 100644 --- a/utils/jztool/src/x1000.c +++ b/utils/jztool/src/x1000.c @@ -100,70 +100,6 @@ static int run_stage2(jz_usbdev* dev, jz_buffer* buf) return jz_usb_start2(dev, load_addr); } -enum { - F_DECOMPRESS = 0x01, - F_OPTIONAL = 0x02, -}; - -static int get_file(jz_context* jz, mtar_t* tar, const char* file, - unsigned int flags, jz_buffer** buf) -{ - jz_buffer* buffer = NULL; - const mtar_header_t* h; - int rc; - - rc = mtar_find(tar, file); - if(rc != MTAR_ESUCCESS) { - if(!(flags & F_OPTIONAL)) - jz_log(jz, JZ_LOG_ERROR, "can't find %s in boot file, tar error %d", file, rc); - return JZ_ERR_OPEN_FILE; - } - - h = mtar_get_header(tar); - buffer = jz_buffer_alloc(h->size, NULL); - if(!buffer) - return JZ_ERR_OUT_OF_MEMORY; - - rc = mtar_read_data(tar, buffer->data, buffer->size); - if(rc < 0 || (unsigned)rc != buffer->size) { - jz_buffer_free(buffer); - jz_log(jz, JZ_LOG_ERROR, "can't read %s in boot file, tar error %d", file, rc); - return JZ_ERR_BAD_FILE_FORMAT; - } - - if(flags & F_DECOMPRESS) { - uint32_t dst_len; - jz_buffer* nbuf = jz_ucl_unpack(buffer->data, buffer->size, &dst_len); - jz_buffer_free(buffer); - if(!nbuf) { - jz_log(jz, JZ_LOG_ERROR, "error decompressing %s in boot file", file); - return JZ_ERR_BAD_FILE_FORMAT; - } - - /* for simplicity just forget original size of buffer */ - nbuf->size = dst_len; - buffer = nbuf; - } - - *buf = buffer; - return JZ_SUCCESS; -} - -static int show_version(jz_context* jz, jz_buffer* info_file) -{ - /* Extract the version string and log it for informational purposes */ - char* boot_version = (char*)info_file->data; - char* endpos = memchr(boot_version, '\n', info_file->size); - if(!endpos) { - jz_log(jz, JZ_LOG_ERROR, "invalid metadata in boot file"); - return JZ_ERR_BAD_FILE_FORMAT; - } - - *endpos = 0; - jz_log(jz, JZ_LOG_NOTICE, "Rockbox bootloader version: %s", boot_version); - return JZ_SUCCESS; -} - /** \brief Load the Rockbox bootloader on an X1000 device * \param dev USB device freshly returned by jz_usb_open() * \param filename Path to the "bootloader.target" file @@ -193,7 +129,7 @@ int jz_x1000_boot(jz_usbdev* dev, jz_device_type type, const char* filename) } /* Extract all necessary files */ - rc = get_file(dev->jz, &tar, spl_filename, false, &spl); + rc = jz_boot_get_file(dev->jz, &tar, spl_filename, false, &spl); if(rc != JZ_SUCCESS) goto error; @@ -206,8 +142,8 @@ int jz_x1000_boot(jz_usbdev* dev, jz_device_type type, const char* filename) */ const char* bl_files[2] = {"bootloader2.ucl", "bootloader.ucl"}; for(int i = 0; i < 2; ++i) { - rc = get_file(dev->jz, &tar, bl_files[i], - F_DECOMPRESS|F_OPTIONAL, &bootloader); + rc = jz_boot_get_file(dev->jz, &tar, bl_files[i], + JZ_BOOT_DECOMPRESS|JZ_BOOT_OPTIONAL, &bootloader); if(rc == JZ_SUCCESS) break; else if(rc != JZ_ERR_OPEN_FILE) @@ -219,12 +155,12 @@ int jz_x1000_boot(jz_usbdev* dev, jz_device_type type, const char* filename) goto error; } - rc = get_file(dev->jz, &tar, "bootloader-info.txt", false, &info_file); + rc = jz_boot_get_file(dev->jz, &tar, "bootloader-info.txt", false, &info_file); if(rc != JZ_SUCCESS) goto error; /* Display the version string */ - rc = show_version(dev->jz, info_file); + rc = jz_boot_show_version(dev->jz, info_file); if(rc != JZ_SUCCESS) goto error; diff --git a/utils/jztool/src/x1600.c b/utils/jztool/src/x1600.c new file mode 100644 index 0000000000..6f82868148 --- /dev/null +++ b/utils/jztool/src/x1600.c @@ -0,0 +1,172 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * Copyright (C) 2026 Michael McAllister + * Derived from src/x1000.c, Copyright (C) 2021 Aidan MacDonald + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +/* X1600 USB recovery uses a separate returning stage1 to initialise DRAM. + * The flash SPL has a different link address and is not used by this path. */ + +#include "jztool.h" +#include "jztool_private.h" +#include "microtar-stdio.h" +#include +#include + +/* Result block written by usbstage1-x1600.c before returning to BootROM. */ +#define X1600_STAGE1_RESULT_ADDR 0x8000f000 +#define X1600_STAGE1_MAGIC 0xc0def00d + +static uint32_t get_le32(const uint8_t* p) +{ + return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | + ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); +} + +/** \brief Load the Rockbox bootloader on an X1600 device + * \param dev USB device freshly returned by jz_usb_open() + * \param type Device type, used to pick the file extension + * \param filename Path to the "bootloader." update package + * \return either JZ_SUCCESS or an error code + */ +int jz_x1600_boot(jz_usbdev* dev, jz_device_type type, const char* filename) +{ + const jz_device_info* dev_info; + const jz_cpu_info* cpu_info; + char stage1_filename[32]; + uint8_t stage1_result[8]; + jz_buffer* stage1 = NULL, *bootloader = NULL, *info_file = NULL; + mtar_t tar; + int rc; + + dev_info = jz_get_device_info(type); + if(!dev_info) + return JZ_ERR_OTHER; + + cpu_info = jz_get_cpu_info(dev_info->cpu_type); + if(!cpu_info) + return JZ_ERR_OTHER; + + /* Load the raw USB stage1, without the flash SPL header. */ + sprintf(stage1_filename, "usbstage1.%s", dev_info->file_ext); + + rc = mtar_open(&tar, filename, "rb"); + if(rc != MTAR_ESUCCESS) { + jz_log(dev->jz, JZ_LOG_ERROR, "cannot open file %s (tar error: %d)", + filename, rc); + return JZ_ERR_OPEN_FILE; + } + + rc = jz_boot_get_file(dev->jz, &tar, stage1_filename, 0, &stage1); + if(rc != JZ_SUCCESS) { + jz_log(dev->jz, JZ_LOG_ERROR, + "This update package has no %s member, so it cannot be USB " + "booted. The X1600 needs a stage1 payload linked at 0x%08lx, " + "separate from the flash SPL.", + stage1_filename, (unsigned long)cpu_info->stage1_load_addr); + goto error; + } + + if(stage1->size > 20 * 1024) { + jz_log(dev->jz, JZ_LOG_ERROR, + "stage1 is %zu bytes; the BootROM's limit is 20 KiB (PM 34.9)", + stage1->size); + rc = JZ_ERR_BAD_FILE_FORMAT; + goto error; + } + + /* The second-stage image is linked at X1600_BOOT_LOAD_ADDR. */ + rc = jz_boot_get_file(dev->jz, &tar, "bootloader2.ucl", JZ_BOOT_DECOMPRESS, &bootloader); + if(rc != JZ_SUCCESS) + goto error; + + rc = jz_boot_get_file(dev->jz, &tar, "bootloader-info.txt", 0, &info_file); + if(rc != JZ_SUCCESS) + goto error; + + rc = jz_boot_show_version(dev->jz, info_file); + if(rc != JZ_SUCCESS) + goto error; + + /* Flush before upload so a previous stage1 is not executed from cache. + * Stage1 initialises DRAM and returns to the BootROM. */ + rc = jz_usb_flush_caches(dev); + if(rc != JZ_SUCCESS) + goto error; + + rc = jz_usb_send(dev, cpu_info->stage1_load_addr, stage1->size, stage1->data); + if(rc != JZ_SUCCESS) + goto error; + + rc = jz_usb_start1(dev, cpu_info->stage1_exec_addr); + if(rc != JZ_SUCCESS) + goto error; + + /* Give stage1 time to finish DRAM training and return. */ + jz_sleepms(500); + + rc = jz_usb_recv(dev, X1600_STAGE1_RESULT_ADDR, + sizeof(stage1_result), stage1_result); + if(rc != JZ_SUCCESS) + goto error; + + if(get_le32(stage1_result) != X1600_STAGE1_MAGIC) { + jz_log(dev->jz, JZ_LOG_ERROR, "Invalid stage1 result"); + rc = JZ_ERR_OTHER; + goto error; + } + + uint32_t status = get_le32(stage1_result + 4); + if(status != 0) { + jz_log(dev->jz, JZ_LOG_ERROR, + "Stage1 DDR initialization failed (status 0x%08lx)", + (unsigned long)status); + rc = JZ_ERR_OTHER; + goto error; + } + + /* Flush before uploading and executing the bootloader. */ + rc = jz_usb_flush_caches(dev); + if(rc != JZ_SUCCESS) + goto error; + + rc = jz_usb_send(dev, cpu_info->stage2_load_addr, + bootloader->size, bootloader->data); + if(rc != JZ_SUCCESS) + goto error; + + rc = jz_usb_flush_caches(dev); + if(rc != JZ_SUCCESS) + goto error; + + rc = jz_usb_start2(dev, cpu_info->stage2_exec_addr); + if(rc != JZ_SUCCESS) + goto error; + + rc = JZ_SUCCESS; + + error: + if(stage1) + jz_buffer_free(stage1); + if(bootloader) + jz_buffer_free(bootloader); + if(info_file) + jz_buffer_free(info_file); + mtar_close(&tar); + return rc; +} diff --git a/utils/mks5lboot/README b/utils/mks5lboot/README index c424f7e617..f2893724ea 100644 --- a/utils/mks5lboot/README +++ b/utils/mks5lboot/README @@ -5,7 +5,13 @@ A tool to install/uninstall a dual bootloader into a s5l8702 based device: - iPod Classic 6G - - iPod Nano 3G (WIP) + - iPod Nano 3G + +Nano 3G support is currently limited to 4GB units with the tested Hynix +A514D3AD NAND configuration. The installer cannot identify the NAND while +the device is in DFU mode; see the Rockbox manual's Nano 3G installation +section before installing. Other configurations are rejected by the +bootloader rather than enabled with untested geometry or programming modes. Usage @@ -45,7 +51,8 @@ Commands: is the name of the platform (type of device) for which the DFU uninstaller will be built. Currently supported platform names are: - ipod6g: iPod Classic 6G + ipod6g: iPod Classic 6G + ipodnano3g: iPod Nano 3G Options: -p, --pid Use a specific (Product Id) USB device, @@ -64,16 +71,17 @@ Dual bootloader installation Prerequisites: - - An iPod Classic 6th with Apple firmware installed and running, current - supported FW versions for existing models: + - An iPod Classic 6th or iPod Nano 3G with Apple firmware installed and + running, current supported FW versions for existing models: Classic 6th 80/160 Late 2007 (1G): 1.1.2 Classic 6th 120 Late 2008 (2G): 2.0.1 Classic 6th 160 Late 2009 (3G): 2.0.4 Classic 6th 160 Late 2012 (4G): 2.0.5 + Nano 3G 4GB: 1.0.1 to 1.1.3 - If your iPod is formated using Apple partitions you must convert this - ipod to FAT32 format (aka a "winpod"), see http://www.rockbox.org/ + iPod to FAT32 format (aka a "winpod"), see http://www.rockbox.org/ wiki/IpodConversionToFAT32 - It is recommended to install the RB firmware before installing the dual diff --git a/utils/mks5lboot/dualboot.c b/utils/mks5lboot/dualboot.c index 4c1608fc41..dbebe29bf9 100644 --- a/utils/mks5lboot/dualboot.c +++ b/utils/mks5lboot/dualboot.c @@ -342,6 +342,333 @@ unsigned char dualboot_install_ipod6g[5396] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +unsigned char dualboot_install_ipodnano3g[5188] = { + 0xd7, 0x00, 0xa0, 0xe3, 0x00, 0xf0, 0x21, 0xe1, 0x24, 0xd0, 0x9f, 0xe5, 0xdb, 0x00, 0xa0, 0xe3, + 0x00, 0xf0, 0x21, 0xe1, 0x18, 0xd0, 0x9f, 0xe5, 0xd3, 0x00, 0xa0, 0xe3, 0x00, 0xf0, 0x21, 0xe1, + 0x10, 0xd0, 0x9f, 0xe5, 0x05, 0x08, 0xa0, 0xe3, 0x78, 0x00, 0x80, 0xe3, 0x10, 0x0f, 0x01, 0xee, + 0x1d, 0x00, 0x00, 0xeb, 0x0c, 0x03, 0x00, 0x22, 0x0c, 0x03, 0x00, 0x22, 0x30, 0x40, 0x2d, 0xe9, + 0x5c, 0x10, 0x9f, 0xe5, 0x14, 0xd0, 0x4d, 0xe2, 0x10, 0x20, 0xa0, 0xe3, 0x0d, 0x00, 0xa0, 0xe1, + 0xb0, 0x04, 0x00, 0xeb, 0x10, 0x30, 0xa0, 0xe3, 0x0d, 0x20, 0xa0, 0xe1, 0x02, 0x10, 0xa0, 0xe3, + 0x00, 0x00, 0xa0, 0xe3, 0xaf, 0x03, 0x00, 0xeb, 0x38, 0x50, 0x9f, 0xe5, 0x00, 0x40, 0xa0, 0xe3, + 0x10, 0x20, 0xa0, 0xe3, 0x05, 0x10, 0xa0, 0xe1, 0x0d, 0x00, 0xa0, 0xe1, 0x72, 0x00, 0x00, 0xeb, + 0x00, 0x00, 0x50, 0xe3, 0x03, 0x00, 0x00, 0x0a, 0x01, 0x40, 0x84, 0xe2, 0x05, 0x00, 0x54, 0xe3, + 0x10, 0x50, 0x85, 0xe2, 0xf5, 0xff, 0xff, 0x1a, 0x04, 0x00, 0xa0, 0xe1, 0x14, 0xd0, 0x8d, 0xe2, + 0x30, 0x80, 0xbd, 0xe8, 0x10, 0x00, 0x02, 0x22, 0x54, 0x16, 0x00, 0x22, 0x70, 0x40, 0x2d, 0xe9, + 0xc3, 0x00, 0x00, 0xeb, 0x68, 0x41, 0x9f, 0xe5, 0x68, 0x01, 0x9f, 0xe5, 0x83, 0x04, 0x00, 0xeb, + 0x04, 0x10, 0xa0, 0xe3, 0x00, 0x00, 0xa0, 0xe3, 0xe8, 0x00, 0x00, 0xeb, 0x0d, 0x30, 0xd4, 0xe5, + 0x0c, 0x20, 0xd4, 0xe5, 0x0f, 0x10, 0xd4, 0xe5, 0x03, 0x24, 0x82, 0xe1, 0x0e, 0x30, 0xd4, 0xe5, + 0x40, 0x50, 0xd4, 0xe5, 0x03, 0x28, 0x82, 0xe1, 0x01, 0x2c, 0x82, 0xe1, 0x10, 0x30, 0x84, 0xe2, + 0x02, 0x1b, 0x84, 0xe2, 0x02, 0x00, 0xa0, 0xe3, 0x22, 0x04, 0x00, 0xeb, 0x40, 0x30, 0x84, 0xe2, + 0x40, 0x20, 0xa0, 0xe3, 0x04, 0x10, 0xa0, 0xe1, 0x02, 0x00, 0xa0, 0xe3, 0x1d, 0x04, 0x00, 0xeb, + 0x00, 0x00, 0x55, 0xe3, 0x0f, 0x00, 0x00, 0x0a, 0x04, 0x10, 0xa0, 0xe1, 0x02, 0x09, 0xa0, 0xe3, + 0x48, 0x03, 0x00, 0xeb, 0x00, 0x00, 0x50, 0xe3, 0x3b, 0x00, 0x00, 0x1a, 0xf8, 0x00, 0x9f, 0xe5, + 0xf8, 0x10, 0x9f, 0xe5, 0xf8, 0x30, 0x9f, 0xe5, 0xb4, 0x20, 0x91, 0xe5, 0x03, 0x00, 0x52, 0xe1, + 0xfc, 0xff, 0xff, 0x3a, 0x61, 0x04, 0x00, 0xeb, 0xf2, 0x35, 0xa0, 0xe3, 0x01, 0x26, 0xa0, 0xe3, + 0x00, 0x20, 0x83, 0xe5, 0xfe, 0xff, 0xff, 0xea, 0xd8, 0x20, 0x9f, 0xe5, 0xd8, 0x10, 0x9f, 0xe5, + 0x02, 0x09, 0xa0, 0xe3, 0xe5, 0x02, 0x00, 0xeb, 0x00, 0x00, 0x50, 0xe3, 0xee, 0xff, 0xff, 0x1a, + 0xb1, 0xff, 0xff, 0xeb, 0x05, 0x00, 0x50, 0xe3, 0x16, 0x00, 0x00, 0x0a, 0xa0, 0x00, 0x9f, 0xe5, + 0xd6, 0x02, 0x00, 0xeb, 0x00, 0x40, 0xa0, 0xe1, 0x46, 0x03, 0x00, 0xeb, 0x04, 0x00, 0x50, 0xe1, + 0xa8, 0x00, 0x9f, 0x35, 0xe5, 0xff, 0xff, 0x3a, 0x9c, 0x10, 0x9f, 0xe5, 0x02, 0x09, 0x84, 0xe2, + 0x28, 0x03, 0x00, 0xeb, 0x00, 0x00, 0x50, 0xe3, 0x16, 0x00, 0x00, 0x1a, 0x84, 0x20, 0x9f, 0xe5, + 0x84, 0x10, 0x9f, 0xe5, 0x01, 0x00, 0xa0, 0xe3, 0x05, 0x04, 0x00, 0xeb, 0x78, 0x10, 0x9f, 0xe5, + 0x02, 0x09, 0xa0, 0xe3, 0x1f, 0x03, 0x00, 0xeb, 0x00, 0x00, 0x50, 0xe3, 0x70, 0x00, 0x9f, 0x05, + 0xd6, 0xff, 0xff, 0x0a, 0xd4, 0xff, 0xff, 0xea, 0x5c, 0x00, 0x9f, 0xe5, 0xbf, 0x02, 0x00, 0xeb, + 0x50, 0x20, 0x9f, 0xe5, 0x50, 0x10, 0x9f, 0xe5, 0x02, 0x09, 0x80, 0xe2, 0xc3, 0x02, 0x00, 0xeb, + 0x00, 0x00, 0x50, 0xe3, 0xcc, 0xff, 0xff, 0x1a, 0x8f, 0xff, 0xff, 0xeb, 0x05, 0x00, 0x50, 0xe3, + 0xdd, 0xff, 0xff, 0x1a, 0xc8, 0xff, 0xff, 0xea, 0x14, 0x10, 0x9f, 0xe5, 0x02, 0x09, 0xa0, 0xe3, + 0x0c, 0x03, 0x00, 0xeb, 0x00, 0x00, 0x50, 0xe3, 0xe3, 0xff, 0xff, 0x0a, 0x24, 0x00, 0x9f, 0xe5, + 0xc2, 0xff, 0xff, 0xea, 0x00, 0x17, 0x00, 0x22, 0xc6, 0x16, 0x00, 0x22, 0xb0, 0x16, 0x00, 0x22, + 0x00, 0x00, 0x70, 0x3c, 0x80, 0x84, 0x1e, 0x00, 0x00, 0x08, 0x02, 0x22, 0x00, 0x00, 0x02, 0x22, + 0xaa, 0x16, 0x00, 0x22, 0xa4, 0x16, 0x00, 0x22, 0xb8, 0x16, 0x00, 0x22, 0x01, 0x10, 0x41, 0xe2, + 0x02, 0x20, 0x80, 0xe0, 0x02, 0x00, 0x50, 0xe1, 0x01, 0x00, 0x00, 0x1a, 0x00, 0x00, 0xa0, 0xe3, + 0x1e, 0xff, 0x2f, 0xe1, 0x00, 0x30, 0xd0, 0xe5, 0x01, 0xc0, 0xf1, 0xe5, 0x0c, 0x00, 0x53, 0xe1, + 0x01, 0x00, 0x00, 0x0a, 0x0c, 0x00, 0x43, 0xe0, 0x1e, 0xff, 0x2f, 0xe1, 0x01, 0x00, 0x80, 0xe2, + 0xf3, 0xff, 0xff, 0xea, 0xc0, 0x32, 0xa0, 0xe1, 0x01, 0x20, 0xa0, 0xe3, 0x1f, 0x00, 0x00, 0xe2, + 0x00, 0x00, 0x51, 0xe3, 0x12, 0x20, 0xa0, 0xe1, 0x01, 0x30, 0x43, 0xe2, 0x0b, 0x00, 0x00, 0x0a, + 0x03, 0x00, 0x53, 0xe3, 0x84, 0x10, 0x9f, 0x95, 0x84, 0x10, 0x9f, 0x85, 0x03, 0x11, 0x91, 0x97, + 0x03, 0x00, 0x53, 0xe3, 0x7c, 0x00, 0x9f, 0x95, 0x00, 0x10, 0x91, 0xe5, 0x03, 0x01, 0x90, 0x97, + 0x6c, 0x00, 0x9f, 0x85, 0x02, 0x30, 0xc1, 0xe1, 0x00, 0x30, 0x80, 0xe5, 0x1e, 0xff, 0x2f, 0xe1, + 0x64, 0x00, 0x9f, 0xe5, 0x64, 0x10, 0x9f, 0xe5, 0x03, 0x00, 0x53, 0xe3, 0x10, 0x00, 0x00, 0x8a, + 0x03, 0x30, 0xd0, 0xe7, 0x03, 0xf1, 0x8f, 0xe0, 0x00, 0x00, 0xa0, 0xe1, 0x6c, 0x10, 0x91, 0xe5, + 0x4c, 0x30, 0x9f, 0xe5, 0x01, 0x20, 0x82, 0xe1, 0x00, 0x20, 0x83, 0xe5, 0x1e, 0xff, 0x2f, 0xe1, + 0x68, 0x10, 0x91, 0xe5, 0x3c, 0x30, 0x9f, 0xe5, 0xf9, 0xff, 0xff, 0xea, 0x58, 0x10, 0x91, 0xe5, + 0x34, 0x30, 0x9f, 0xe5, 0xf6, 0xff, 0xff, 0xea, 0x4c, 0x10, 0x91, 0xe5, 0x2c, 0x30, 0x9f, 0xe5, + 0xf3, 0xff, 0xff, 0xea, 0x48, 0x10, 0x91, 0xe5, 0x04, 0x30, 0x9f, 0xe5, 0xf0, 0xff, 0xff, 0xea, + 0xe0, 0x16, 0x00, 0x22, 0x48, 0x00, 0x50, 0x3c, 0xe0, 0x16, 0x00, 0x22, 0xdc, 0x16, 0x00, 0x22, + 0x00, 0x00, 0x50, 0x3c, 0x6c, 0x00, 0x50, 0x3c, 0x68, 0x00, 0x50, 0x3c, 0x58, 0x00, 0x50, 0x3c, + 0x4c, 0x00, 0x50, 0x3c, 0x04, 0xe0, 0x2d, 0xe5, 0x04, 0xc0, 0x9d, 0xe5, 0x01, 0x10, 0x21, 0xe2, + 0x01, 0xc0, 0x4c, 0xe2, 0x0c, 0xc2, 0xa0, 0xe1, 0xff, 0xc0, 0x0c, 0xe2, 0x02, 0x26, 0xa0, 0xe1, + 0x81, 0xc7, 0x8c, 0xe1, 0x03, 0x2a, 0x02, 0xe2, 0x01, 0x30, 0x43, 0xe2, 0x02, 0xc0, 0x8c, 0xe1, + 0x0f, 0x30, 0x03, 0xe2, 0x03, 0xc0, 0x8c, 0xe1, 0x0c, 0x38, 0xa0, 0xe1, 0x03, 0xc0, 0xc0, 0xe3, + 0x00, 0x20, 0x9c, 0xe5, 0x24, 0xe0, 0x9f, 0xe5, 0x80, 0x11, 0xa0, 0xe1, 0x10, 0x10, 0x01, 0xe2, + 0x3e, 0x21, 0x02, 0xe0, 0x23, 0x38, 0xa0, 0xe1, 0x13, 0x21, 0x82, 0xe1, 0x00, 0x20, 0x8c, 0xe5, + 0xb0, 0x20, 0xd0, 0xe1, 0x03, 0x00, 0x52, 0xe1, 0xfc, 0xff, 0xff, 0x1a, 0x04, 0xf0, 0x9d, 0xe4, + 0x00, 0x00, 0xff, 0xff, 0x60, 0x20, 0x9f, 0xe5, 0x13, 0x40, 0x2d, 0xe9, 0x44, 0x30, 0x92, 0xe5, + 0x01, 0x40, 0xa0, 0xe3, 0x01, 0x3c, 0xc3, 0xe3, 0x44, 0x30, 0x82, 0xe5, 0x04, 0x10, 0xa0, 0xe1, + 0x04, 0x30, 0xa0, 0xe1, 0x00, 0x20, 0xa0, 0xe3, 0x40, 0x00, 0x9f, 0xe5, 0x00, 0x40, 0x8d, 0xe5, + 0xd7, 0xff, 0xff, 0xeb, 0x04, 0x10, 0xa0, 0xe1, 0x25, 0x00, 0xa0, 0xe3, 0xa0, 0xff, 0xff, 0xeb, + 0x2c, 0x30, 0x9f, 0xe5, 0x11, 0x2d, 0xa0, 0xe3, 0xa0, 0x20, 0x83, 0xe5, 0x0b, 0x20, 0xa0, 0xe3, + 0xb0, 0x20, 0x83, 0xe5, 0x00, 0x20, 0xe0, 0xe3, 0xa8, 0x20, 0x83, 0xe5, 0x03, 0x20, 0xa0, 0xe3, + 0xa4, 0x20, 0x83, 0xe5, 0x08, 0xd0, 0x8d, 0xe2, 0x10, 0x80, 0xbd, 0xe8, 0x00, 0x00, 0x50, 0x3c, + 0x12, 0x00, 0x50, 0x3c, 0x00, 0x00, 0x70, 0x3c, 0x24, 0x20, 0x9f, 0xe5, 0x02, 0x00, 0x50, 0xe3, + 0x20, 0x30, 0x9f, 0x05, 0x02, 0x00, 0x00, 0x0a, 0x01, 0x00, 0x50, 0xe3, 0x18, 0x30, 0x9f, 0xe5, + 0x02, 0x30, 0xa0, 0x01, 0x00, 0x30, 0x93, 0xe5, 0x3e, 0x0c, 0x13, 0xe3, 0xf6, 0xff, 0xff, 0x0a, + 0x1e, 0xff, 0x2f, 0xe1, 0x08, 0x00, 0xe0, 0x3c, 0x08, 0x00, 0x20, 0x3d, 0x08, 0x00, 0x30, 0x3c, + 0x04, 0x30, 0x9f, 0xe5, 0x00, 0x11, 0x83, 0xe7, 0x1e, 0xff, 0x2f, 0xe1, 0xd0, 0x16, 0x00, 0x22, + 0x7c, 0x30, 0x9f, 0xe5, 0x00, 0x00, 0x51, 0xe3, 0x78, 0x20, 0x9f, 0xe5, 0x03, 0x20, 0xa0, 0x11, + 0x01, 0x00, 0x50, 0xe3, 0x09, 0x00, 0x00, 0x0a, 0x02, 0x00, 0x50, 0xe3, 0x0e, 0x00, 0x00, 0x0a, + 0x00, 0x00, 0x50, 0xe3, 0x1e, 0xff, 0x2f, 0x11, 0x5c, 0x10, 0x9f, 0xe5, 0x00, 0x30, 0x91, 0xe5, + 0x23, 0x38, 0xa0, 0xe1, 0x03, 0x38, 0x82, 0xe1, 0x00, 0x30, 0x81, 0xe5, 0x1e, 0xff, 0x2f, 0xe1, + 0x44, 0x10, 0x9f, 0xe5, 0xc0, 0x30, 0x91, 0xe5, 0x03, 0x38, 0xa0, 0xe1, 0x23, 0x38, 0xa0, 0xe1, + 0x02, 0x38, 0x83, 0xe1, 0xc0, 0x30, 0x81, 0xe5, 0x1e, 0xff, 0x2f, 0xe1, 0x28, 0x10, 0x9f, 0xe5, + 0xc0, 0x31, 0x91, 0xe5, 0xff, 0x34, 0xc3, 0xe3, 0x02, 0x3c, 0x83, 0xe1, 0xc0, 0x31, 0x81, 0xe5, + 0xe0, 0x31, 0x91, 0xe5, 0xff, 0x30, 0xc3, 0xe3, 0x22, 0x34, 0x83, 0xe1, 0xe0, 0x31, 0x81, 0xe5, + 0x1e, 0xff, 0x2f, 0xe1, 0x22, 0x22, 0x00, 0x00, 0xef, 0xee, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3c, + 0x01, 0x00, 0x50, 0xe3, 0x01, 0x10, 0x21, 0xe2, 0x06, 0x00, 0x00, 0x0a, 0x02, 0x00, 0x50, 0xe3, + 0x09, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x50, 0xe3, 0x24, 0x30, 0x9f, 0x05, 0x0e, 0x10, 0x81, 0x03, + 0x00, 0x12, 0x83, 0x05, 0x1e, 0xff, 0x2f, 0xe1, 0x18, 0x30, 0x9f, 0xe5, 0x10, 0x20, 0x9f, 0xe5, + 0x03, 0x30, 0x81, 0xe1, 0x00, 0x32, 0x82, 0xe5, 0x1e, 0xff, 0x2f, 0xe1, 0x08, 0x30, 0x9f, 0xe5, + 0xf9, 0xff, 0xff, 0xea, 0x00, 0x00, 0xf0, 0x3c, 0x0e, 0x04, 0x06, 0x00, 0x0e, 0x06, 0x0e, 0x00, + 0x02, 0x00, 0x50, 0xe3, 0x10, 0x40, 0x2d, 0xe9, 0x00, 0x40, 0xa0, 0xe1, 0x30, 0x00, 0xa0, 0x03, + 0x02, 0x00, 0x00, 0x0a, 0x01, 0x00, 0x54, 0xe3, 0x22, 0x00, 0xa0, 0x13, 0x2b, 0x00, 0xa0, 0x03, + 0x01, 0x10, 0xa0, 0xe3, 0x3e, 0xff, 0xff, 0xeb, 0x02, 0x00, 0x54, 0xe3, 0x98, 0x30, 0x9f, 0x05, + 0x0f, 0x20, 0xa0, 0xe3, 0x02, 0x00, 0x00, 0x0a, 0x01, 0x00, 0x54, 0xe3, 0x8c, 0x30, 0x9f, 0x05, + 0x8c, 0x30, 0x9f, 0x15, 0x08, 0x20, 0x83, 0xe5, 0x00, 0x20, 0x93, 0xe5, 0x02, 0x00, 0x54, 0xe3, + 0x0c, 0x20, 0x82, 0xe3, 0x00, 0x20, 0x83, 0xe5, 0x78, 0x30, 0x9f, 0xe5, 0x78, 0x20, 0x9f, 0xe5, + 0x04, 0x11, 0x93, 0xe7, 0x11, 0x00, 0x00, 0x1a, 0x5c, 0x30, 0x9f, 0xe5, 0x30, 0x10, 0x83, 0xe5, + 0x06, 0x10, 0xa0, 0xe3, 0x0c, 0x10, 0x83, 0xe5, 0x04, 0x20, 0x83, 0xe5, 0x00, 0x20, 0x93, 0xe5, + 0x02, 0x00, 0x54, 0xe3, 0x0c, 0x20, 0x82, 0xe3, 0x00, 0x20, 0x83, 0xe5, 0x38, 0x30, 0x9f, 0x05, + 0x03, 0x00, 0x00, 0x0a, 0x34, 0x20, 0x9f, 0xe5, 0x01, 0x00, 0x54, 0xe3, 0x30, 0x30, 0x9f, 0xe5, + 0x02, 0x30, 0xa0, 0x01, 0x01, 0x20, 0xa0, 0xe3, 0x00, 0x20, 0x83, 0xe5, 0x10, 0x80, 0xbd, 0xe8, + 0x01, 0x00, 0x54, 0xe3, 0x14, 0x30, 0x9f, 0x05, 0x14, 0x30, 0x9f, 0x15, 0x06, 0x00, 0xa0, 0xe3, + 0x30, 0x10, 0x83, 0xe5, 0x0c, 0x00, 0x83, 0xe5, 0xea, 0xff, 0xff, 0xea, 0x00, 0x00, 0x20, 0x3d, + 0x00, 0x00, 0xe0, 0x3c, 0x00, 0x00, 0x30, 0x3c, 0xd0, 0x16, 0x00, 0x22, 0x18, 0x06, 0x01, 0x00, + 0x02, 0x00, 0x50, 0xe3, 0x30, 0x00, 0xa0, 0x03, 0x02, 0x00, 0x00, 0x0a, 0x01, 0x00, 0x50, 0xe3, + 0x22, 0x00, 0xa0, 0x13, 0x2b, 0x00, 0xa0, 0x03, 0x00, 0x10, 0xa0, 0xe3, 0x08, 0xff, 0xff, 0xea, + 0x02, 0x00, 0x50, 0xe3, 0x90, 0x30, 0x9f, 0x05, 0x10, 0x40, 0x2d, 0xe9, 0x03, 0x00, 0x00, 0x0a, + 0x88, 0x20, 0x9f, 0xe5, 0x01, 0x00, 0x50, 0xe3, 0x84, 0x30, 0x9f, 0xe5, 0x02, 0x30, 0xa0, 0x01, + 0x01, 0x20, 0xa0, 0xe3, 0x00, 0x20, 0x83, 0xe5, 0x78, 0x20, 0x9f, 0xe5, 0x02, 0x00, 0x50, 0xe3, + 0x74, 0x30, 0x9f, 0x05, 0x02, 0x00, 0x00, 0x0a, 0x01, 0x00, 0x50, 0xe3, 0x6c, 0x30, 0x9f, 0xe5, + 0x02, 0x30, 0xa0, 0x01, 0x00, 0x30, 0x93, 0xe5, 0x1f, 0x3e, 0x03, 0xe2, 0x01, 0x0c, 0x53, 0xe3, + 0xf5, 0xff, 0xff, 0x0a, 0x02, 0x00, 0x50, 0xe3, 0x54, 0x30, 0x9f, 0x05, 0x03, 0x00, 0x00, 0x0a, + 0x50, 0x20, 0x9f, 0xe5, 0x01, 0x00, 0x50, 0xe3, 0x4c, 0x30, 0x9f, 0xe5, 0x02, 0x30, 0xa0, 0x01, + 0x00, 0x10, 0x83, 0xe5, 0x57, 0xff, 0xff, 0xeb, 0x02, 0x00, 0x50, 0xe3, 0x3c, 0x30, 0x9f, 0x05, + 0x03, 0x00, 0x00, 0x0a, 0x38, 0x20, 0x9f, 0xe5, 0x01, 0x00, 0x50, 0xe3, 0x34, 0x30, 0x9f, 0xe5, + 0x02, 0x30, 0xa0, 0x01, 0x00, 0x00, 0x93, 0xe5, 0x10, 0x80, 0xbd, 0xe8, 0x34, 0x00, 0x20, 0x3d, + 0x34, 0x00, 0xe0, 0x3c, 0x34, 0x00, 0x30, 0x3c, 0x08, 0x00, 0xe0, 0x3c, 0x08, 0x00, 0x20, 0x3d, + 0x08, 0x00, 0x30, 0x3c, 0x10, 0x00, 0x20, 0x3d, 0x10, 0x00, 0xe0, 0x3c, 0x10, 0x00, 0x30, 0x3c, + 0x20, 0x00, 0x20, 0x3d, 0x20, 0x00, 0xe0, 0x3c, 0x20, 0x00, 0x30, 0x3c, 0x02, 0x00, 0x50, 0xe3, + 0x70, 0x40, 0x2d, 0xe9, 0x02, 0x40, 0xa0, 0xe1, 0x11, 0x00, 0x00, 0x1a, 0xb0, 0x30, 0x9f, 0xe5, + 0xb0, 0x20, 0x9f, 0xe5, 0x34, 0x10, 0x83, 0xe5, 0x04, 0x30, 0x93, 0xe5, 0xa8, 0x50, 0x9f, 0xe5, + 0x01, 0x30, 0x83, 0xe3, 0x01, 0x10, 0x84, 0xe0, 0x00, 0x30, 0x82, 0xe5, 0x01, 0x00, 0x54, 0xe1, + 0x11, 0x00, 0x00, 0x1a, 0x02, 0x00, 0x50, 0xe3, 0x19, 0x00, 0x00, 0x1a, 0x80, 0x30, 0x9f, 0xe5, + 0x80, 0x20, 0x9f, 0xe5, 0x04, 0x30, 0x93, 0xe5, 0x01, 0x30, 0xc3, 0xe3, 0x00, 0x30, 0x82, 0xe5, + 0x70, 0x80, 0xbd, 0xe8, 0x01, 0x00, 0x50, 0xe3, 0x70, 0x30, 0x9f, 0x05, 0x70, 0x30, 0x9f, 0x15, + 0x34, 0x10, 0x83, 0x05, 0x34, 0x10, 0x83, 0x15, 0x04, 0x30, 0x93, 0x05, 0x64, 0x20, 0x9f, 0x05, + 0x04, 0x30, 0x93, 0x15, 0x60, 0x20, 0x9f, 0x15, 0xe7, 0xff, 0xff, 0xea, 0x21, 0xff, 0xff, 0xeb, + 0x02, 0x00, 0x50, 0xe3, 0x54, 0x30, 0x9f, 0x05, 0x02, 0x00, 0x00, 0x0a, 0x01, 0x00, 0x50, 0xe3, + 0x4c, 0x30, 0x9f, 0xe5, 0x05, 0x30, 0xa0, 0x01, 0x00, 0x30, 0x93, 0xe5, 0x01, 0x30, 0xc4, 0xe4, + 0xe1, 0xff, 0xff, 0xea, 0x01, 0x00, 0x50, 0xe3, 0x20, 0x30, 0x9f, 0x05, 0x20, 0x30, 0x9f, 0x15, + 0x04, 0x30, 0x93, 0x05, 0x1c, 0x20, 0x9f, 0x05, 0x04, 0x30, 0x93, 0x15, 0x18, 0x20, 0x9f, 0x15, + 0xe0, 0xff, 0xff, 0xea, 0x00, 0x00, 0x20, 0x3d, 0x04, 0x00, 0x20, 0x3d, 0x20, 0x00, 0xe0, 0x3c, + 0x00, 0x00, 0xe0, 0x3c, 0x00, 0x00, 0x30, 0x3c, 0x04, 0x00, 0xe0, 0x3c, 0x04, 0x00, 0x30, 0x3c, + 0x20, 0x00, 0x20, 0x3d, 0x20, 0x00, 0x30, 0x3c, 0x01, 0x20, 0xd0, 0xe5, 0x00, 0x30, 0xd0, 0xe5, + 0x02, 0x34, 0x83, 0xe1, 0x02, 0x20, 0xd0, 0xe5, 0x03, 0x00, 0xd0, 0xe5, 0x02, 0x38, 0x83, 0xe1, + 0x00, 0x0c, 0x83, 0xe1, 0x1e, 0xff, 0x2f, 0xe1, 0x10, 0x40, 0x2d, 0xe9, 0x00, 0x40, 0xa0, 0xe1, + 0x01, 0x10, 0xa0, 0xe3, 0x04, 0x00, 0xa0, 0xe1, 0x30, 0xff, 0xff, 0xeb, 0x05, 0x10, 0xa0, 0xe3, + 0x04, 0x00, 0xa0, 0xe1, 0x81, 0xff, 0xff, 0xeb, 0xff, 0x10, 0xa0, 0xe3, 0x04, 0x00, 0xa0, 0xe1, + 0x7e, 0xff, 0xff, 0xeb, 0x01, 0x10, 0x10, 0xe2, 0x03, 0x00, 0x00, 0x0a, 0x00, 0x10, 0xa0, 0xe3, + 0x04, 0x00, 0xa0, 0xe1, 0x25, 0xff, 0xff, 0xeb, 0xf0, 0xff, 0xff, 0xea, 0x04, 0x00, 0xa0, 0xe1, + 0x10, 0x40, 0xbd, 0xe8, 0x21, 0xff, 0xff, 0xea, 0x70, 0x40, 0x2d, 0xe9, 0x00, 0x50, 0x51, 0xe2, + 0x00, 0x40, 0xa0, 0xe1, 0x07, 0x00, 0x00, 0x1a, 0x01, 0x10, 0xa0, 0xe3, 0x1b, 0xff, 0xff, 0xeb, + 0x04, 0x10, 0xa0, 0xe3, 0x04, 0x00, 0xa0, 0xe1, 0x6c, 0xff, 0xff, 0xeb, 0x05, 0x10, 0xa0, 0xe1, + 0x04, 0x00, 0xa0, 0xe1, 0x15, 0xff, 0xff, 0xeb, 0x01, 0x10, 0xa0, 0xe3, 0x04, 0x00, 0xa0, 0xe1, + 0x12, 0xff, 0xff, 0xeb, 0x50, 0x10, 0xa0, 0xe3, 0x04, 0x00, 0xa0, 0xe1, 0x63, 0xff, 0xff, 0xeb, + 0x00, 0x10, 0xa0, 0xe3, 0x04, 0x00, 0xa0, 0xe1, 0x0c, 0xff, 0xff, 0xeb, 0x01, 0x10, 0xa0, 0xe3, + 0x04, 0x00, 0xa0, 0xe1, 0x09, 0xff, 0xff, 0xeb, 0x01, 0x10, 0xa0, 0xe3, 0x04, 0x00, 0xa0, 0xe1, + 0x5a, 0xff, 0xff, 0xeb, 0x00, 0x00, 0x55, 0xe3, 0x1c, 0x10, 0xa0, 0x03, 0x00, 0x10, 0xa0, 0x13, + 0x04, 0x00, 0xa0, 0xe1, 0x55, 0xff, 0xff, 0xeb, 0x00, 0x10, 0xa0, 0xe3, 0x04, 0x00, 0xa0, 0xe1, + 0xfe, 0xfe, 0xff, 0xeb, 0x00, 0x00, 0x55, 0xe3, 0x70, 0x80, 0xbd, 0x08, 0x04, 0x00, 0xa0, 0xe1, + 0x01, 0x10, 0xa0, 0xe3, 0xf9, 0xfe, 0xff, 0xeb, 0x06, 0x10, 0xa0, 0xe3, 0x04, 0x00, 0xa0, 0xe1, + 0x4a, 0xff, 0xff, 0xeb, 0x04, 0x00, 0xa0, 0xe1, 0x00, 0x10, 0xa0, 0xe3, 0x70, 0x40, 0xbd, 0xe8, + 0xf2, 0xfe, 0xff, 0xea, 0x70, 0x40, 0x2d, 0xe9, 0x00, 0x40, 0xa0, 0xe1, 0x01, 0x50, 0xa0, 0xe1, + 0x02, 0xff, 0xff, 0xeb, 0x04, 0x00, 0xa0, 0xe1, 0xb6, 0xff, 0xff, 0xeb, 0x04, 0x00, 0xa0, 0xe1, + 0x01, 0x10, 0xa0, 0xe3, 0xc7, 0xff, 0xff, 0xeb, 0x04, 0x00, 0xa0, 0xe1, 0x01, 0x10, 0xa0, 0xe3, + 0xe6, 0xfe, 0xff, 0xeb, 0x20, 0x10, 0xa0, 0xe3, 0x04, 0x00, 0xa0, 0xe1, 0x37, 0xff, 0xff, 0xeb, + 0x25, 0x18, 0xa0, 0xe1, 0xff, 0x10, 0x01, 0xe2, 0x04, 0x00, 0xa0, 0xe1, 0x33, 0xff, 0xff, 0xeb, + 0x25, 0x14, 0xa0, 0xe1, 0xff, 0x10, 0x01, 0xe2, 0x04, 0x00, 0xa0, 0xe1, 0x2f, 0xff, 0xff, 0xeb, + 0x00, 0x10, 0xa0, 0xe3, 0x04, 0x00, 0xa0, 0xe1, 0x2c, 0xff, 0xff, 0xeb, 0x04, 0x00, 0xa0, 0xe1, + 0x00, 0x10, 0xa0, 0xe3, 0xd5, 0xfe, 0xff, 0xeb, 0x04, 0x00, 0xa0, 0xe1, 0x00, 0x10, 0xa0, 0xe3, + 0xb0, 0xff, 0xff, 0xeb, 0x04, 0x00, 0xa0, 0xe1, 0x70, 0x40, 0xbd, 0xe8, 0x1b, 0xff, 0xff, 0xea, + 0x10, 0x40, 0x2d, 0xe9, 0x01, 0x10, 0xa0, 0xe3, 0x00, 0x40, 0xa0, 0xe1, 0xa7, 0xfe, 0xff, 0xeb, + 0x04, 0x00, 0xa0, 0xe1, 0x00, 0x10, 0xa0, 0xe3, 0x10, 0x40, 0xbd, 0xe8, 0xc7, 0xfe, 0xff, 0xea, + 0x00, 0x10, 0xa0, 0xe3, 0xa1, 0xfe, 0xff, 0xea, 0xf0, 0x41, 0x2d, 0xe9, 0x00, 0x40, 0xa0, 0xe1, + 0x03, 0x70, 0xa0, 0xe1, 0x02, 0x60, 0xa0, 0xe1, 0x01, 0x50, 0xa0, 0xe1, 0xd3, 0xfe, 0xff, 0xeb, + 0x04, 0x00, 0xa0, 0xe1, 0x87, 0xff, 0xff, 0xeb, 0x04, 0x00, 0xa0, 0xe1, 0x01, 0x10, 0xa0, 0xe3, + 0xba, 0xfe, 0xff, 0xeb, 0x03, 0x10, 0xa0, 0xe3, 0x04, 0x00, 0xa0, 0xe1, 0x0b, 0xff, 0xff, 0xeb, + 0x25, 0x18, 0xa0, 0xe1, 0xff, 0x10, 0x01, 0xe2, 0x04, 0x00, 0xa0, 0xe1, 0x07, 0xff, 0xff, 0xeb, + 0x25, 0x14, 0xa0, 0xe1, 0xff, 0x10, 0x01, 0xe2, 0x04, 0x00, 0xa0, 0xe1, 0x03, 0xff, 0xff, 0xeb, + 0xff, 0x10, 0x05, 0xe2, 0x04, 0x00, 0xa0, 0xe1, 0x00, 0xff, 0xff, 0xeb, 0x07, 0x20, 0xa0, 0xe1, + 0x06, 0x10, 0xa0, 0xe1, 0x04, 0x00, 0xa0, 0xe1, 0x2f, 0xff, 0xff, 0xeb, 0x04, 0x00, 0xa0, 0xe1, + 0x00, 0x10, 0xa0, 0xe3, 0xa5, 0xfe, 0xff, 0xeb, 0x04, 0x00, 0xa0, 0xe1, 0xf0, 0x41, 0xbd, 0xe8, + 0xee, 0xfe, 0xff, 0xea, 0xf0, 0x47, 0x2d, 0xe9, 0x00, 0x40, 0xa0, 0xe1, 0x20, 0xd0, 0x4d, 0xe2, + 0x01, 0x60, 0xa0, 0xe1, 0x03, 0x50, 0xa0, 0xe1, 0x03, 0x70, 0x82, 0xe0, 0xaf, 0xfe, 0xff, 0xeb, + 0x04, 0x00, 0xa0, 0xe1, 0x63, 0xff, 0xff, 0xeb, 0x01, 0x10, 0xa0, 0xe3, 0x04, 0x00, 0xa0, 0xe1, + 0x96, 0xfe, 0xff, 0xeb, 0x03, 0x10, 0xa0, 0xe3, 0x04, 0x00, 0xa0, 0xe1, 0xe7, 0xfe, 0xff, 0xeb, + 0x46, 0x18, 0xa0, 0xe1, 0xff, 0x10, 0x01, 0xe2, 0x04, 0x00, 0xa0, 0xe1, 0xe3, 0xfe, 0xff, 0xeb, + 0x46, 0x14, 0xa0, 0xe1, 0xff, 0x10, 0x01, 0xe2, 0x04, 0x00, 0xa0, 0xe1, 0xdf, 0xfe, 0xff, 0xeb, + 0xff, 0x10, 0x06, 0xe2, 0x04, 0x00, 0xa0, 0xe1, 0xdc, 0xfe, 0xff, 0xeb, 0x00, 0x60, 0xa0, 0xe3, + 0x00, 0x00, 0x55, 0xe3, 0x07, 0x00, 0x00, 0xca, 0x00, 0x10, 0xa0, 0xe3, 0x04, 0x00, 0xa0, 0xe1, + 0x82, 0xfe, 0xff, 0xeb, 0x04, 0x00, 0xa0, 0xe1, 0xcc, 0xfe, 0xff, 0xeb, 0x06, 0x00, 0xa0, 0xe1, + 0x20, 0xd0, 0x8d, 0xe2, 0xf0, 0x87, 0xbd, 0xe8, 0x20, 0x00, 0x55, 0xe3, 0x05, 0x90, 0xa0, 0xb1, + 0x20, 0x90, 0xa0, 0xa3, 0x0d, 0x20, 0xa0, 0xe1, 0x09, 0x10, 0xa0, 0xe1, 0x04, 0x00, 0xa0, 0xe1, + 0xfd, 0xfe, 0xff, 0xeb, 0x09, 0x20, 0xa0, 0xe1, 0x0d, 0x10, 0xa0, 0xe1, 0x05, 0x00, 0x47, 0xe0, + 0xc1, 0xfd, 0xff, 0xeb, 0x00, 0x30, 0xa0, 0xe3, 0x00, 0x00, 0x50, 0xe3, 0x01, 0x60, 0x86, 0x13, + 0x03, 0x20, 0xdd, 0xe7, 0x02, 0x30, 0x83, 0xe2, 0xff, 0x00, 0x52, 0xe3, 0x02, 0x60, 0x86, 0x13, + 0x03, 0x00, 0x59, 0xe1, 0xf9, 0xff, 0xff, 0xca, 0x20, 0x50, 0x45, 0xe2, 0xdf, 0xff, 0xff, 0xea, + 0xf7, 0x4f, 0x2d, 0xe9, 0x00, 0x40, 0xa0, 0xe1, 0x01, 0x70, 0xa0, 0xe1, 0x02, 0x50, 0xa0, 0xe1, + 0x03, 0x80, 0xa0, 0xe1, 0x00, 0x00, 0x58, 0xe3, 0x01, 0x00, 0x00, 0xca, 0x0c, 0xd0, 0x8d, 0xe2, + 0xf0, 0x8f, 0xbd, 0xe8, 0x07, 0x6a, 0xa0, 0xe1, 0x26, 0x6a, 0xa0, 0xe1, 0x01, 0x6a, 0x66, 0xe2, + 0x08, 0x00, 0x56, 0xe1, 0x08, 0x60, 0xa0, 0xa1, 0x06, 0x30, 0xa0, 0xe1, 0x05, 0x20, 0xa0, 0xe1, + 0x07, 0x10, 0xa0, 0xe1, 0x04, 0x00, 0xa0, 0xe1, 0xb1, 0xff, 0xff, 0xeb, 0x06, 0xa0, 0x85, 0xe0, + 0x01, 0x00, 0x10, 0xe3, 0x0d, 0x00, 0x00, 0x0a, 0x02, 0x00, 0x10, 0xe3, 0x03, 0x00, 0x00, 0x0a, + 0xff, 0x1e, 0xc7, 0xe3, 0x0f, 0x10, 0xc1, 0xe3, 0x04, 0x00, 0xa0, 0xe1, 0x58, 0xff, 0xff, 0xeb, + 0x06, 0x90, 0xa0, 0xe1, 0x05, 0x30, 0xa0, 0xe1, 0x00, 0x20, 0xd3, 0xe5, 0xff, 0x00, 0x52, 0xe3, + 0x06, 0x00, 0x00, 0x1a, 0x01, 0x30, 0x83, 0xe2, 0x0a, 0x00, 0x53, 0xe1, 0xf9, 0xff, 0xff, 0x1a, + 0x0a, 0x50, 0xa0, 0xe1, 0x06, 0x70, 0x87, 0xe0, 0x06, 0x80, 0x48, 0xe0, 0xdc, 0xff, 0xff, 0xea, + 0x04, 0x00, 0xa0, 0xe1, 0x51, 0xfe, 0xff, 0xeb, 0x04, 0x00, 0xa0, 0xe1, 0x05, 0xff, 0xff, 0xeb, + 0x27, 0xb8, 0xa0, 0xe1, 0x01, 0x10, 0xa0, 0xe3, 0x04, 0x00, 0xa0, 0xe1, 0x15, 0xff, 0xff, 0xeb, + 0x02, 0x50, 0x85, 0xe2, 0x01, 0x30, 0xa0, 0xe3, 0xff, 0xb0, 0x0b, 0xe2, 0x01, 0x10, 0xa0, 0xe3, + 0x04, 0x00, 0xa0, 0xe1, 0x04, 0x30, 0x8d, 0xe5, 0x30, 0xfe, 0xff, 0xeb, 0xad, 0x10, 0xa0, 0xe3, + 0x04, 0x00, 0xa0, 0xe1, 0x81, 0xfe, 0xff, 0xeb, 0x04, 0x30, 0x9d, 0xe5, 0x00, 0x00, 0x53, 0xe3, + 0x09, 0x00, 0x00, 0x0a, 0x0b, 0x10, 0xa0, 0xe1, 0x04, 0x00, 0xa0, 0xe1, 0x7b, 0xfe, 0xff, 0xeb, + 0x27, 0x14, 0xa0, 0xe1, 0xff, 0x10, 0x01, 0xe2, 0x04, 0x00, 0xa0, 0xe1, 0x77, 0xfe, 0xff, 0xeb, + 0xff, 0x10, 0x07, 0xe2, 0x04, 0x00, 0xa0, 0xe1, 0x74, 0xfe, 0xff, 0xeb, 0x02, 0x10, 0x55, 0xe5, + 0x04, 0x00, 0xa0, 0xe1, 0x71, 0xfe, 0xff, 0xeb, 0x01, 0x10, 0x55, 0xe5, 0x04, 0x00, 0xa0, 0xe1, + 0x6e, 0xfe, 0xff, 0xeb, 0x00, 0x10, 0xa0, 0xe3, 0x04, 0x00, 0xa0, 0xe1, 0x17, 0xfe, 0xff, 0xeb, + 0x04, 0x00, 0xa0, 0xe1, 0xdf, 0xfe, 0xff, 0xeb, 0x02, 0x90, 0x59, 0xe2, 0x02, 0x50, 0x85, 0xe2, + 0x00, 0x30, 0xa0, 0xe3, 0xdc, 0xff, 0xff, 0x1a, 0x04, 0x00, 0xa0, 0xe1, 0x09, 0x10, 0xa0, 0xe1, + 0xec, 0xfe, 0xff, 0xeb, 0x04, 0x00, 0xa0, 0xe1, 0x58, 0xfe, 0xff, 0xeb, 0xc7, 0xff, 0xff, 0xea, + 0x10, 0x40, 0x2d, 0xe9, 0x0c, 0x00, 0x80, 0xe2, 0xca, 0xfe, 0xff, 0xeb, 0x5f, 0x0d, 0x80, 0xe2, + 0x3f, 0x00, 0x80, 0xe2, 0xff, 0x0e, 0xc0, 0xe3, 0x0f, 0x00, 0xc0, 0xe3, 0x10, 0x80, 0xbd, 0xe8, + 0xff, 0x41, 0x2d, 0xe9, 0x01, 0x50, 0xa0, 0xe1, 0x00, 0x80, 0xa0, 0xe1, 0x00, 0x00, 0xa0, 0xe3, + 0x02, 0x60, 0xa0, 0xe1, 0x2d, 0xff, 0xff, 0xeb, 0x05, 0x30, 0xa0, 0xe1, 0x02, 0x2b, 0xa0, 0xe3, + 0x08, 0x10, 0xa0, 0xe1, 0x00, 0x00, 0xa0, 0xe3, 0x32, 0xff, 0xff, 0xeb, 0x00, 0x00, 0xa0, 0xe3, + 0x2e, 0xff, 0xff, 0xeb, 0x08, 0x11, 0x9f, 0xe5, 0x04, 0x20, 0xa0, 0xe3, 0x05, 0x00, 0xa0, 0xe1, + 0x41, 0xfd, 0xff, 0xeb, 0x00, 0x00, 0x50, 0xe3, 0x00, 0x40, 0xe0, 0x13, 0x37, 0x00, 0x00, 0x1a, + 0x0d, 0x30, 0xa0, 0xe1, 0x40, 0x20, 0xa0, 0xe3, 0x05, 0x10, 0xa0, 0xe1, 0x02, 0x00, 0xa0, 0xe3, + 0x08, 0x01, 0x00, 0xeb, 0x10, 0x20, 0xa0, 0xe3, 0x40, 0x10, 0x85, 0xe2, 0x0d, 0x00, 0xa0, 0xe1, + 0x35, 0xfd, 0xff, 0xeb, 0x00, 0x40, 0x50, 0xe2, 0x01, 0x40, 0xe0, 0x13, 0x2b, 0x00, 0x00, 0x1a, + 0x0c, 0x00, 0x85, 0xe2, 0xa3, 0xfe, 0xff, 0xeb, 0x7e, 0x0b, 0x50, 0xe3, 0x00, 0x70, 0xa0, 0xe1, + 0x29, 0x00, 0x00, 0x8a, 0x08, 0x00, 0x85, 0xe2, 0x9e, 0xfe, 0xff, 0xeb, 0x07, 0x00, 0x50, 0xe1, + 0x25, 0x00, 0x00, 0x8a, 0x07, 0x30, 0xd5, 0xe5, 0x01, 0x30, 0x43, 0xe2, 0xff, 0x30, 0x03, 0xe2, + 0x01, 0x00, 0x53, 0xe3, 0x03, 0x40, 0xe0, 0x83, 0x1c, 0x00, 0x00, 0x8a, 0x00, 0x00, 0x56, 0xe3, + 0x1a, 0x00, 0x00, 0x0a, 0x04, 0x00, 0xa0, 0xe1, 0x00, 0xff, 0xff, 0xeb, 0x06, 0x30, 0xa0, 0xe1, + 0x07, 0x20, 0xa0, 0xe1, 0x02, 0x1b, 0x88, 0xe2, 0x04, 0x00, 0xa0, 0xe1, 0x05, 0xff, 0xff, 0xeb, + 0x04, 0x00, 0xa0, 0xe1, 0x01, 0xff, 0xff, 0xeb, 0x07, 0x30, 0xd5, 0xe5, 0x01, 0x00, 0x53, 0xe3, + 0x03, 0x00, 0x00, 0x1a, 0x06, 0x20, 0xa0, 0xe1, 0x05, 0x10, 0xa0, 0xe1, 0x04, 0x00, 0xa0, 0xe1, + 0xf3, 0x00, 0x00, 0xeb, 0x0d, 0x30, 0xa0, 0xe1, 0x07, 0x20, 0xa0, 0xe1, 0x06, 0x10, 0xa0, 0xe1, + 0x02, 0x00, 0xa0, 0xe3, 0xdb, 0x00, 0x00, 0xeb, 0x10, 0x20, 0xa0, 0xe3, 0x02, 0x10, 0x85, 0xe0, + 0x0d, 0x00, 0xa0, 0xe1, 0x08, 0xfd, 0xff, 0xeb, 0x00, 0x00, 0x50, 0xe3, 0x04, 0x40, 0xe0, 0x13, + 0x04, 0x00, 0xa0, 0xe1, 0x10, 0xd0, 0x8d, 0xe2, 0xf0, 0x81, 0xbd, 0xe8, 0x02, 0x40, 0xe0, 0xe3, + 0xfa, 0xff, 0xff, 0xea, 0xf0, 0x16, 0x00, 0x22, 0x70, 0x40, 0x2d, 0xe9, 0x00, 0x60, 0xa0, 0xe1, + 0x0c, 0x00, 0x81, 0xe2, 0x6f, 0xfe, 0xff, 0xeb, 0x01, 0x50, 0xa0, 0xe1, 0x02, 0x4b, 0x80, 0xe2, + 0x00, 0x00, 0xa0, 0xe3, 0xd9, 0xfe, 0xff, 0xeb, 0x04, 0x30, 0xa0, 0xe1, 0x05, 0x20, 0xa0, 0xe1, + 0x06, 0x10, 0xa0, 0xe1, 0x00, 0x00, 0xa0, 0xe3, 0x3c, 0xff, 0xff, 0xeb, 0x04, 0x30, 0xa0, 0xe1, + 0x05, 0x20, 0xa0, 0xe1, 0x06, 0x10, 0xa0, 0xe1, 0x00, 0x00, 0xa0, 0xe3, 0xfc, 0xfe, 0xff, 0xeb, + 0x00, 0x40, 0xa0, 0xe1, 0x00, 0x00, 0xa0, 0xe3, 0xd4, 0xfe, 0xff, 0xeb, 0x04, 0x00, 0xe0, 0xe1, + 0x01, 0x00, 0x00, 0xe2, 0x70, 0x80, 0xbd, 0xe8, 0x70, 0x40, 0x2d, 0xe9, 0x68, 0x40, 0x9f, 0xe5, + 0x28, 0xd0, 0x4d, 0xe2, 0x00, 0x00, 0xa0, 0xe3, 0xc4, 0xfe, 0xff, 0xeb, 0x5c, 0x60, 0x9f, 0xe5, + 0x04, 0x50, 0xa0, 0xe1, 0x0d, 0x30, 0xa0, 0xe1, 0x28, 0x20, 0xa0, 0xe3, 0x04, 0x10, 0xa0, 0xe1, + 0x00, 0x00, 0xa0, 0xe3, 0xc7, 0xfe, 0xff, 0xeb, 0x44, 0x10, 0x9f, 0xe5, 0x04, 0x20, 0xa0, 0xe3, + 0x0d, 0x00, 0xa0, 0xe1, 0xd8, 0xfc, 0xff, 0xeb, 0x00, 0x00, 0x50, 0xe3, 0x05, 0x00, 0x00, 0x1a, + 0x0c, 0x30, 0x9d, 0xe5, 0x28, 0x40, 0x84, 0xe2, 0x03, 0x00, 0x55, 0xe1, 0x03, 0x50, 0xa0, 0x21, + 0x06, 0x00, 0x54, 0xe1, 0xee, 0xff, 0xff, 0x1a, 0x00, 0x00, 0xa0, 0xe3, 0xb7, 0xfe, 0xff, 0xeb, + 0x0a, 0x09, 0x45, 0xe2, 0x28, 0xd0, 0x8d, 0xe2, 0x70, 0x80, 0xbd, 0xe8, 0x00, 0xfe, 0x0f, 0x00, + 0xe0, 0xff, 0x0f, 0x00, 0xf5, 0x16, 0x00, 0x22, 0xf0, 0x47, 0x2d, 0xe9, 0x00, 0x80, 0xa0, 0xe1, + 0x01, 0x90, 0xa0, 0xe1, 0x0a, 0x00, 0xa0, 0xe3, 0x01, 0x10, 0xa0, 0xe3, 0xe3, 0x45, 0xa0, 0xe3, + 0x03, 0x50, 0xa0, 0xe1, 0x02, 0x60, 0xa0, 0xe1, 0x01, 0x70, 0xa0, 0xe3, 0xcc, 0xfc, 0xff, 0xeb, + 0x00, 0x00, 0xa0, 0xe3, 0x74, 0x00, 0x84, 0xe5, 0x78, 0x00, 0x84, 0xe5, 0x7c, 0x00, 0x84, 0xe5, + 0x80, 0x00, 0x84, 0xe5, 0x08, 0x70, 0x84, 0xe5, 0x08, 0x00, 0x84, 0xe5, 0x00, 0x70, 0x84, 0xe5, + 0x10, 0x00, 0x84, 0xe5, 0x6c, 0x90, 0x84, 0xe5, 0x6c, 0x30, 0x94, 0xe5, 0x07, 0x00, 0x58, 0xe1, + 0x03, 0x30, 0xe0, 0xe1, 0x88, 0x30, 0x84, 0xe5, 0x08, 0x30, 0xa0, 0x13, 0x09, 0x30, 0xa0, 0x03, + 0x8c, 0x00, 0x84, 0xe5, 0x14, 0x30, 0x84, 0xe5, 0x18, 0x50, 0x84, 0xe5, 0x20, 0x60, 0x84, 0xe5, + 0x24, 0x50, 0x84, 0xe5, 0x28, 0x60, 0x84, 0xe5, 0x2c, 0x50, 0x84, 0xe5, 0x30, 0x60, 0x84, 0xe5, + 0x34, 0x50, 0x84, 0xe5, 0xde, 0x00, 0x00, 0xeb, 0x04, 0x70, 0x84, 0xe5, 0x0c, 0x30, 0x94, 0xe5, + 0x0f, 0x00, 0x13, 0xe3, 0xfc, 0xff, 0xff, 0x0a, 0xf0, 0x47, 0xbd, 0xe8, 0x00, 0x10, 0xa0, 0xe3, + 0x0a, 0x00, 0xa0, 0xe3, 0xaa, 0xfc, 0xff, 0xea, 0xf0, 0x4f, 0x2d, 0xe9, 0x00, 0x60, 0xa0, 0xe1, + 0x54, 0xd0, 0x4d, 0xe2, 0x01, 0x40, 0xa0, 0xe1, 0x00, 0x00, 0xa0, 0xe3, 0x01, 0x10, 0xa0, 0xe3, + 0x02, 0x50, 0xa0, 0xe1, 0xa2, 0xfc, 0xff, 0xeb, 0x0e, 0x23, 0xa0, 0xe3, 0x01, 0x30, 0xa0, 0xe3, + 0x04, 0x30, 0x82, 0xe5, 0x00, 0x30, 0x92, 0xe5, 0x01, 0x30, 0x13, 0xe2, 0xfc, 0xff, 0xff, 0x1a, + 0x04, 0x30, 0x82, 0xe5, 0x00, 0x30, 0x82, 0xe5, 0x40, 0x20, 0x64, 0xe2, 0x06, 0x20, 0x42, 0xe0, + 0x50, 0x71, 0x9f, 0xe5, 0x04, 0x20, 0x8d, 0xe5, 0x04, 0x20, 0x86, 0xe0, 0x08, 0x20, 0x8d, 0xe5, + 0x06, 0x00, 0xa0, 0xe1, 0xa4, 0x2e, 0xa0, 0xe1, 0xa4, 0x9a, 0xa0, 0xe1, 0xa4, 0xa6, 0xa0, 0xe1, + 0x0e, 0x13, 0xa0, 0xe3, 0x0c, 0x20, 0x8d, 0xe5, 0x04, 0x20, 0x9d, 0xe5, 0x00, 0x60, 0x82, 0xe0, + 0x00, 0x00, 0x56, 0xe3, 0x3e, 0x00, 0x00, 0xda, 0x10, 0x20, 0x8d, 0xe2, 0x02, 0xe0, 0xa0, 0xe1, + 0x00, 0xc0, 0xa0, 0xe3, 0x01, 0xc0, 0x8c, 0xe2, 0x10, 0x00, 0x5c, 0xe3, 0x04, 0x30, 0x8e, 0xe4, + 0xfb, 0xff, 0xff, 0x1a, 0x40, 0x00, 0x56, 0xe3, 0x30, 0x00, 0x00, 0xda, 0x0c, 0xc0, 0x9d, 0xe5, + 0x4c, 0x90, 0xcd, 0xe5, 0x4b, 0xc0, 0xcd, 0xe5, 0xa4, 0xc2, 0xa0, 0xe1, 0x4e, 0xc0, 0xcd, 0xe5, + 0x84, 0xc1, 0xa0, 0xe1, 0x4f, 0xc0, 0xcd, 0xe5, 0x01, 0xc0, 0xa0, 0xe3, 0x4d, 0xa0, 0xcd, 0xe5, + 0xd4, 0xe0, 0x9f, 0xe5, 0x04, 0x60, 0x92, 0xe4, 0x04, 0x60, 0x8e, 0xe4, 0x07, 0x00, 0x5e, 0xe1, + 0xfb, 0xff, 0xff, 0x1a, 0x00, 0x20, 0x91, 0xe5, 0x02, 0x20, 0x82, 0xe3, 0x00, 0x20, 0x81, 0xe5, + 0x00, 0x20, 0x91, 0xe5, 0x01, 0x00, 0x12, 0xe3, 0xfc, 0xff, 0xff, 0x1a, 0x00, 0x20, 0x91, 0xe5, + 0x00, 0x00, 0x5c, 0xe3, 0x08, 0x20, 0x82, 0xe3, 0x00, 0x20, 0x81, 0xe5, 0xd9, 0xff, 0xff, 0x0a, + 0x98, 0x30, 0x9f, 0xe5, 0x98, 0x20, 0x9f, 0xe5, 0x04, 0x10, 0x93, 0xe4, 0x02, 0x00, 0x53, 0xe1, + 0x04, 0x10, 0x85, 0xe4, 0xfb, 0xff, 0xff, 0x1a, 0x00, 0x10, 0xa0, 0xe3, 0x01, 0x00, 0xa0, 0xe1, + 0x5f, 0xfc, 0xff, 0xeb, 0x54, 0xd0, 0x8d, 0xe2, 0xf0, 0x8f, 0xbd, 0xe8, 0x0e, 0xb0, 0xd0, 0xe7, + 0x01, 0xe0, 0x8e, 0xe2, 0x01, 0xb0, 0xc8, 0xe4, 0x0e, 0x00, 0x5c, 0xe1, 0xfa, 0xff, 0xff, 0xca, + 0x50, 0xc0, 0x8c, 0xe2, 0x0c, 0xc0, 0x8d, 0xe0, 0x7f, 0xe0, 0xe0, 0xe3, 0x07, 0x00, 0x56, 0xe3, + 0x40, 0xe0, 0x4c, 0xe5, 0xd0, 0xff, 0xff, 0xca, 0x00, 0xc0, 0xa0, 0xe3, 0xd7, 0xff, 0xff, 0xea, + 0x08, 0xc0, 0x9d, 0xe5, 0x02, 0x80, 0xa0, 0xe1, 0x00, 0xe0, 0xa0, 0xe3, 0x00, 0xc0, 0x4c, 0xe0, + 0xf0, 0xff, 0xff, 0xea, 0x20, 0x20, 0x9f, 0xe5, 0x04, 0xc0, 0x40, 0xe2, 0x04, 0xe0, 0xbc, 0xe5, + 0x04, 0xe0, 0x82, 0xe4, 0x07, 0x00, 0x52, 0xe1, 0xfb, 0xff, 0xff, 0x1a, 0x40, 0x00, 0x80, 0xe2, + 0x00, 0xc0, 0xa0, 0xe3, 0xce, 0xff, 0xff, 0xea, 0x80, 0x00, 0x00, 0x38, 0x40, 0x00, 0x00, 0x38, + 0x20, 0x00, 0x00, 0x38, 0x34, 0x00, 0x00, 0x38, 0x30, 0x40, 0x2d, 0xe9, 0x1c, 0xd0, 0x4d, 0xe2, + 0x03, 0x40, 0xa0, 0xe1, 0x00, 0x50, 0xa0, 0xe1, 0x01, 0x00, 0xa0, 0xe1, 0x02, 0x10, 0xa0, 0xe1, + 0x04, 0x20, 0x8d, 0xe2, 0x8b, 0xff, 0xff, 0xeb, 0x04, 0x10, 0x8d, 0xe2, 0x10, 0x20, 0xa0, 0xe3, + 0x04, 0x00, 0xa0, 0xe1, 0x57, 0x00, 0x00, 0xeb, 0x10, 0x30, 0xa0, 0xe3, 0x04, 0x20, 0xa0, 0xe1, + 0x05, 0x10, 0xa0, 0xe1, 0x01, 0x00, 0xa0, 0xe3, 0x56, 0xff, 0xff, 0xeb, 0x1c, 0xd0, 0x8d, 0xe2, + 0x30, 0x80, 0xbd, 0xe8, 0x70, 0x40, 0x2d, 0xe9, 0x0d, 0x30, 0xd1, 0xe5, 0x0c, 0x40, 0xd1, 0xe5, + 0x01, 0x00, 0x50, 0xe3, 0x03, 0x44, 0x84, 0xe1, 0x0e, 0x30, 0xd1, 0xe5, 0x00, 0x50, 0xa0, 0xe1, + 0x03, 0x48, 0x84, 0xe1, 0x0f, 0x30, 0xd1, 0xe5, 0x02, 0x60, 0xa0, 0xe1, 0x03, 0x4c, 0x84, 0xe1, + 0x02, 0x30, 0xa0, 0x13, 0x01, 0x30, 0xa0, 0x03, 0x07, 0x30, 0xc1, 0xe5, 0x40, 0x20, 0xa0, 0xe3, + 0x40, 0x30, 0x81, 0xe2, 0x02, 0x00, 0xa0, 0xe3, 0xda, 0xff, 0xff, 0xeb, 0x04, 0x30, 0xa0, 0xe1, + 0x06, 0x20, 0xa0, 0xe1, 0x05, 0x00, 0xa0, 0xe1, 0x02, 0x10, 0xa0, 0xe3, 0x70, 0x40, 0xbd, 0xe8, + 0x3c, 0xff, 0xff, 0xea, 0x1c, 0x20, 0x9f, 0xe5, 0x00, 0x00, 0x50, 0xe3, 0x00, 0x30, 0x92, 0xe5, + 0xee, 0x14, 0xa0, 0x03, 0xff, 0x34, 0xc3, 0xe3, 0x53, 0x14, 0xa0, 0x13, 0x01, 0x30, 0x83, 0xe1, + 0x00, 0x30, 0x82, 0xe5, 0x1e, 0xff, 0x2f, 0xe1, 0x00, 0x00, 0xf0, 0x3c, 0x70, 0x40, 0x2d, 0xe9, + 0x60, 0x40, 0x9f, 0xe5, 0xfa, 0x3f, 0xa0, 0xe3, 0xb4, 0x50, 0x94, 0xe5, 0x00, 0x60, 0xa0, 0xe1, + 0x01, 0x00, 0xa0, 0xe3, 0x93, 0x51, 0x25, 0xe0, 0xed, 0xff, 0xff, 0xeb, 0x48, 0x10, 0x9f, 0xe5, + 0x00, 0x30, 0xa0, 0xe3, 0xb4, 0x20, 0x94, 0xe5, 0x05, 0x00, 0x52, 0xe1, 0x02, 0x00, 0x00, 0xba, + 0x70, 0x40, 0xbd, 0xe8, 0x00, 0x00, 0xa0, 0xe3, 0xe5, 0xff, 0xff, 0xea, 0x01, 0x20, 0x23, 0xe2, + 0x06, 0x3c, 0x23, 0xe2, 0x0f, 0x30, 0x23, 0xe2, 0x00, 0x32, 0x81, 0xe5, 0xb4, 0x30, 0x94, 0xe5, + 0xa6, 0x30, 0x83, 0xe0, 0xb4, 0x00, 0x94, 0xe5, 0x03, 0x00, 0x50, 0xe1, 0xfc, 0xff, 0xff, 0xba, + 0x02, 0x30, 0xa0, 0xe1, 0xee, 0xff, 0xff, 0xea, 0x00, 0x00, 0x70, 0x3c, 0x00, 0x00, 0xf0, 0x3c, + 0x70, 0x40, 0x2d, 0xe9, 0x38, 0x50, 0x9f, 0xe5, 0x06, 0x40, 0x80, 0xe2, 0xfa, 0x6f, 0xa0, 0xe3, + 0xb6, 0x00, 0x54, 0xe1, 0x00, 0x00, 0x50, 0xe3, 0x70, 0x80, 0xbd, 0x08, 0xb4, 0x10, 0x54, 0xe1, + 0xd9, 0xff, 0xff, 0xeb, 0xb4, 0x30, 0x95, 0xe5, 0xb2, 0x20, 0x54, 0xe1, 0x96, 0x32, 0x23, 0xe0, + 0xb4, 0x20, 0x95, 0xe5, 0x03, 0x00, 0x52, 0xe1, 0xfc, 0xff, 0xff, 0xba, 0x06, 0x40, 0x84, 0xe2, + 0xf2, 0xff, 0xff, 0xea, 0x00, 0x00, 0x70, 0x3c, 0x01, 0x30, 0x40, 0xe2, 0x02, 0x20, 0x81, 0xe0, + 0x02, 0x00, 0x51, 0xe1, 0x1e, 0xff, 0x2f, 0x01, 0x01, 0xc0, 0xd1, 0xe4, 0x01, 0xc0, 0xe3, 0xe5, + 0xfa, 0xff, 0xff, 0xea, 0x7e, 0xff, 0x17, 0xee, 0xfd, 0xff, 0xff, 0x1a, 0x00, 0x10, 0xa0, 0xe3, + 0x9a, 0x1f, 0x07, 0xee, 0x1e, 0xff, 0x2f, 0xe1, 0xc1, 0x3e, 0xbc, 0x68, 0x7c, 0xcd, 0xe8, 0x22, + 0xad, 0xbd, 0x88, 0x94, 0xa9, 0x95, 0x21, 0x4b, 0xc4, 0x57, 0xfd, 0x04, 0x8e, 0xfb, 0xf1, 0x1a, + 0x9d, 0xfa, 0x80, 0x11, 0x1e, 0x3f, 0xa6, 0x3e, 0x2e, 0x7f, 0xa3, 0x4a, 0xf1, 0xaa, 0x75, 0x4b, + 0x34, 0xf7, 0xb9, 0xc4, 0x58, 0xcf, 0xbd, 0xdb, 0x15, 0xa6, 0x6a, 0xbf, 0xa4, 0x3d, 0xfa, 0x76, + 0x9f, 0xce, 0xce, 0x39, 0x1f, 0x28, 0xeb, 0x47, 0x60, 0xac, 0x5a, 0x12, 0x38, 0x65, 0x0d, 0x2b, + 0xc6, 0x63, 0x15, 0x02, 0xa0, 0x44, 0x84, 0x39, 0xb8, 0x0b, 0xf4, 0x01, 0xf4, 0x01, 0xb8, 0x0b, + 0xf4, 0x01, 0xf4, 0x01, 0xb8, 0x0b, 0xf4, 0x01, 0x00, 0x00, 0x00, 0x00, 0xe8, 0x03, 0x64, 0x00, + 0x00, 0x00, 0xf4, 0x01, 0x96, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf4, 0x01, 0x64, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x0b, 0x08, 0x05, 0x00, 0x4c, 0x00, 0x50, 0x3c, 0x58, 0x00, 0x50, 0x3c, 0x68, 0x00, 0x50, 0x3c, + 0x6c, 0x00, 0x50, 0x3c, 0x38, 0x37, 0x30, 0x32, 0x00, 0x68, 0x73, 0x6c, 0x66, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x38, 0x37, 0x30, 0x32, 0x31, 0x2e, 0x30, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 +}; unsigned char dualboot_uninstall_ipod6g[5076] = { 0xd7, 0x00, 0xa0, 0xe3, 0x00, 0xf0, 0x21, 0xe1, 0x24, 0xd0, 0x9f, 0xe5, 0xdb, 0x00, 0xa0, 0xe3, 0x00, 0xf0, 0x21, 0xe1, 0x18, 0xd0, 0x9f, 0xe5, 0xd3, 0x00, 0xa0, 0xe3, 0x00, 0xf0, 0x21, 0xe1, @@ -662,3 +989,311 @@ unsigned char dualboot_uninstall_ipod6g[5076] = { 0x68, 0x73, 0x6c, 0x66, 0x00, 0x38, 0x37, 0x30, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +unsigned char dualboot_uninstall_ipodnano3g[4884] = { + 0xd7, 0x00, 0xa0, 0xe3, 0x00, 0xf0, 0x21, 0xe1, 0x24, 0xd0, 0x9f, 0xe5, 0xdb, 0x00, 0xa0, 0xe3, + 0x00, 0xf0, 0x21, 0xe1, 0x18, 0xd0, 0x9f, 0xe5, 0xd3, 0x00, 0xa0, 0xe3, 0x00, 0xf0, 0x21, 0xe1, + 0x10, 0xd0, 0x9f, 0xe5, 0x05, 0x08, 0xa0, 0xe3, 0x78, 0x00, 0x80, 0xe3, 0x10, 0x0f, 0x01, 0xee, + 0x1d, 0x00, 0x00, 0xeb, 0x0c, 0x03, 0x00, 0x22, 0x0c, 0x03, 0x00, 0x22, 0x30, 0x40, 0x2d, 0xe9, + 0x5c, 0x10, 0x9f, 0xe5, 0x14, 0xd0, 0x4d, 0xe2, 0x10, 0x20, 0xa0, 0xe3, 0x0d, 0x00, 0xa0, 0xe1, + 0x7a, 0x04, 0x00, 0xeb, 0x10, 0x30, 0xa0, 0xe3, 0x0d, 0x20, 0xa0, 0xe1, 0x02, 0x10, 0xa0, 0xe3, + 0x00, 0x00, 0xa0, 0xe3, 0x79, 0x03, 0x00, 0xeb, 0x38, 0x50, 0x9f, 0xe5, 0x00, 0x40, 0xa0, 0xe3, + 0x10, 0x20, 0xa0, 0xe3, 0x05, 0x10, 0xa0, 0xe1, 0x0d, 0x00, 0xa0, 0xe1, 0x50, 0x00, 0x00, 0xeb, + 0x00, 0x00, 0x50, 0xe3, 0x03, 0x00, 0x00, 0x0a, 0x01, 0x40, 0x84, 0xe2, 0x05, 0x00, 0x54, 0xe3, + 0x10, 0x50, 0x85, 0xe2, 0xf5, 0xff, 0xff, 0x1a, 0x04, 0x00, 0xa0, 0xe1, 0x14, 0xd0, 0x8d, 0xe2, + 0x30, 0x80, 0xbd, 0xe8, 0x10, 0x00, 0x02, 0x22, 0x7c, 0x15, 0x00, 0x22, 0x70, 0x40, 0x2d, 0xe9, + 0xa1, 0x00, 0x00, 0xeb, 0xe8, 0x00, 0x9f, 0xe5, 0x4e, 0x04, 0x00, 0xeb, 0x04, 0x10, 0xa0, 0xe3, + 0x00, 0x00, 0xa0, 0xe3, 0xc7, 0x00, 0x00, 0xeb, 0xd8, 0x10, 0x9f, 0xe5, 0x00, 0x20, 0xa0, 0xe3, + 0x02, 0x09, 0xa0, 0xe3, 0xf3, 0x02, 0x00, 0xeb, 0x00, 0x00, 0x50, 0xe3, 0x0a, 0x00, 0x00, 0x0a, + 0xc4, 0x00, 0x9f, 0xe5, 0xc4, 0x10, 0x9f, 0xe5, 0xc4, 0x30, 0x9f, 0xe5, 0xb4, 0x20, 0x91, 0xe5, + 0x03, 0x00, 0x52, 0xe1, 0xfc, 0xff, 0xff, 0x3a, 0x3e, 0x04, 0x00, 0xeb, 0xf2, 0x35, 0xa0, 0xe3, + 0x01, 0x26, 0xa0, 0xe3, 0x00, 0x20, 0x83, 0xe5, 0xfe, 0xff, 0xff, 0xea, 0xca, 0xff, 0xff, 0xeb, + 0x05, 0x00, 0x50, 0xe3, 0x20, 0x00, 0x00, 0x1a, 0x88, 0x00, 0x9f, 0xe5, 0xd9, 0x02, 0x00, 0xeb, + 0x90, 0x20, 0x9f, 0xe5, 0x7c, 0x10, 0x9f, 0xe5, 0x00, 0x50, 0xa0, 0xe1, 0x02, 0x09, 0x80, 0xe2, + 0xdc, 0x02, 0x00, 0xeb, 0x00, 0x40, 0x50, 0xe2, 0xe8, 0xff, 0xff, 0x1a, 0xbe, 0xff, 0xff, 0xeb, + 0x05, 0x00, 0x50, 0xe3, 0xe5, 0xff, 0xff, 0x0a, 0x68, 0x20, 0x9f, 0xe5, 0x54, 0x10, 0x9f, 0xe5, + 0x01, 0x00, 0xa0, 0xe3, 0xe8, 0x03, 0x00, 0xeb, 0x48, 0x10, 0x9f, 0xe5, 0x02, 0x09, 0xa0, 0xe3, + 0x22, 0x03, 0x00, 0xeb, 0x00, 0x00, 0x50, 0xe3, 0x4c, 0x00, 0x9f, 0x05, 0xdc, 0xff, 0xff, 0x0a, + 0x04, 0x00, 0xa0, 0xe1, 0xf3, 0x01, 0x00, 0xeb, 0x28, 0x00, 0x9f, 0xe5, 0xc1, 0x02, 0x00, 0xeb, + 0x25, 0x26, 0xa0, 0xe1, 0x02, 0x19, 0x80, 0xe2, 0x21, 0x16, 0xa0, 0xe1, 0x04, 0x00, 0xa0, 0xe1, + 0x19, 0x02, 0x00, 0xeb, 0x04, 0x00, 0xa0, 0xe1, 0xf2, 0x01, 0x00, 0xeb, 0x1c, 0x00, 0x9f, 0xe5, + 0xcf, 0xff, 0xff, 0xea, 0xee, 0x15, 0x00, 0x22, 0x00, 0x00, 0x02, 0x22, 0xd8, 0x15, 0x00, 0x22, + 0x00, 0x00, 0x70, 0x3c, 0x80, 0x84, 0x1e, 0x00, 0x00, 0x08, 0x02, 0x22, 0xcc, 0x15, 0x00, 0x22, + 0xe0, 0x15, 0x00, 0x22, 0x01, 0x10, 0x41, 0xe2, 0x02, 0x20, 0x80, 0xe0, 0x02, 0x00, 0x50, 0xe1, + 0x01, 0x00, 0x00, 0x1a, 0x00, 0x00, 0xa0, 0xe3, 0x1e, 0xff, 0x2f, 0xe1, 0x00, 0x30, 0xd0, 0xe5, + 0x01, 0xc0, 0xf1, 0xe5, 0x0c, 0x00, 0x53, 0xe1, 0x01, 0x00, 0x00, 0x0a, 0x0c, 0x00, 0x43, 0xe0, + 0x1e, 0xff, 0x2f, 0xe1, 0x01, 0x00, 0x80, 0xe2, 0xf3, 0xff, 0xff, 0xea, 0xc0, 0x32, 0xa0, 0xe1, + 0x01, 0x20, 0xa0, 0xe3, 0x1f, 0x00, 0x00, 0xe2, 0x00, 0x00, 0x51, 0xe3, 0x12, 0x20, 0xa0, 0xe1, + 0x01, 0x30, 0x43, 0xe2, 0x0b, 0x00, 0x00, 0x0a, 0x03, 0x00, 0x53, 0xe3, 0x84, 0x10, 0x9f, 0x95, + 0x84, 0x10, 0x9f, 0x85, 0x03, 0x11, 0x91, 0x97, 0x03, 0x00, 0x53, 0xe3, 0x7c, 0x00, 0x9f, 0x95, + 0x00, 0x10, 0x91, 0xe5, 0x03, 0x01, 0x90, 0x97, 0x6c, 0x00, 0x9f, 0x85, 0x02, 0x30, 0xc1, 0xe1, + 0x00, 0x30, 0x80, 0xe5, 0x1e, 0xff, 0x2f, 0xe1, 0x64, 0x00, 0x9f, 0xe5, 0x64, 0x10, 0x9f, 0xe5, + 0x03, 0x00, 0x53, 0xe3, 0x10, 0x00, 0x00, 0x8a, 0x03, 0x30, 0xd0, 0xe7, 0x03, 0xf1, 0x8f, 0xe0, + 0x00, 0x00, 0xa0, 0xe1, 0x6c, 0x10, 0x91, 0xe5, 0x4c, 0x30, 0x9f, 0xe5, 0x01, 0x20, 0x82, 0xe1, + 0x00, 0x20, 0x83, 0xe5, 0x1e, 0xff, 0x2f, 0xe1, 0x68, 0x10, 0x91, 0xe5, 0x3c, 0x30, 0x9f, 0xe5, + 0xf9, 0xff, 0xff, 0xea, 0x58, 0x10, 0x91, 0xe5, 0x34, 0x30, 0x9f, 0xe5, 0xf6, 0xff, 0xff, 0xea, + 0x4c, 0x10, 0x91, 0xe5, 0x2c, 0x30, 0x9f, 0xe5, 0xf3, 0xff, 0xff, 0xea, 0x48, 0x10, 0x91, 0xe5, + 0x04, 0x30, 0x9f, 0xe5, 0xf0, 0xff, 0xff, 0xea, 0x08, 0x16, 0x00, 0x22, 0x48, 0x00, 0x50, 0x3c, + 0x08, 0x16, 0x00, 0x22, 0x04, 0x16, 0x00, 0x22, 0x00, 0x00, 0x50, 0x3c, 0x6c, 0x00, 0x50, 0x3c, + 0x68, 0x00, 0x50, 0x3c, 0x58, 0x00, 0x50, 0x3c, 0x4c, 0x00, 0x50, 0x3c, 0x04, 0xe0, 0x2d, 0xe5, + 0x04, 0xc0, 0x9d, 0xe5, 0x01, 0x10, 0x21, 0xe2, 0x01, 0xc0, 0x4c, 0xe2, 0x0c, 0xc2, 0xa0, 0xe1, + 0xff, 0xc0, 0x0c, 0xe2, 0x02, 0x26, 0xa0, 0xe1, 0x81, 0xc7, 0x8c, 0xe1, 0x03, 0x2a, 0x02, 0xe2, + 0x01, 0x30, 0x43, 0xe2, 0x02, 0xc0, 0x8c, 0xe1, 0x0f, 0x30, 0x03, 0xe2, 0x03, 0xc0, 0x8c, 0xe1, + 0x0c, 0x38, 0xa0, 0xe1, 0x03, 0xc0, 0xc0, 0xe3, 0x00, 0x20, 0x9c, 0xe5, 0x24, 0xe0, 0x9f, 0xe5, + 0x80, 0x11, 0xa0, 0xe1, 0x10, 0x10, 0x01, 0xe2, 0x3e, 0x21, 0x02, 0xe0, 0x23, 0x38, 0xa0, 0xe1, + 0x13, 0x21, 0x82, 0xe1, 0x00, 0x20, 0x8c, 0xe5, 0xb0, 0x20, 0xd0, 0xe1, 0x03, 0x00, 0x52, 0xe1, + 0xfc, 0xff, 0xff, 0x1a, 0x04, 0xf0, 0x9d, 0xe4, 0x00, 0x00, 0xff, 0xff, 0x60, 0x20, 0x9f, 0xe5, + 0x13, 0x40, 0x2d, 0xe9, 0x44, 0x30, 0x92, 0xe5, 0x01, 0x40, 0xa0, 0xe3, 0x01, 0x3c, 0xc3, 0xe3, + 0x44, 0x30, 0x82, 0xe5, 0x04, 0x10, 0xa0, 0xe1, 0x04, 0x30, 0xa0, 0xe1, 0x00, 0x20, 0xa0, 0xe3, + 0x40, 0x00, 0x9f, 0xe5, 0x00, 0x40, 0x8d, 0xe5, 0xd7, 0xff, 0xff, 0xeb, 0x04, 0x10, 0xa0, 0xe1, + 0x25, 0x00, 0xa0, 0xe3, 0xa0, 0xff, 0xff, 0xeb, 0x2c, 0x30, 0x9f, 0xe5, 0x11, 0x2d, 0xa0, 0xe3, + 0xa0, 0x20, 0x83, 0xe5, 0x0b, 0x20, 0xa0, 0xe3, 0xb0, 0x20, 0x83, 0xe5, 0x00, 0x20, 0xe0, 0xe3, + 0xa8, 0x20, 0x83, 0xe5, 0x03, 0x20, 0xa0, 0xe3, 0xa4, 0x20, 0x83, 0xe5, 0x08, 0xd0, 0x8d, 0xe2, + 0x10, 0x80, 0xbd, 0xe8, 0x00, 0x00, 0x50, 0x3c, 0x12, 0x00, 0x50, 0x3c, 0x00, 0x00, 0x70, 0x3c, + 0x24, 0x20, 0x9f, 0xe5, 0x02, 0x00, 0x50, 0xe3, 0x20, 0x30, 0x9f, 0x05, 0x02, 0x00, 0x00, 0x0a, + 0x01, 0x00, 0x50, 0xe3, 0x18, 0x30, 0x9f, 0xe5, 0x02, 0x30, 0xa0, 0x01, 0x00, 0x30, 0x93, 0xe5, + 0x3e, 0x0c, 0x13, 0xe3, 0xf6, 0xff, 0xff, 0x0a, 0x1e, 0xff, 0x2f, 0xe1, 0x08, 0x00, 0xe0, 0x3c, + 0x08, 0x00, 0x20, 0x3d, 0x08, 0x00, 0x30, 0x3c, 0x04, 0x30, 0x9f, 0xe5, 0x00, 0x11, 0x83, 0xe7, + 0x1e, 0xff, 0x2f, 0xe1, 0xf8, 0x15, 0x00, 0x22, 0x7c, 0x30, 0x9f, 0xe5, 0x00, 0x00, 0x51, 0xe3, + 0x78, 0x20, 0x9f, 0xe5, 0x03, 0x20, 0xa0, 0x11, 0x01, 0x00, 0x50, 0xe3, 0x09, 0x00, 0x00, 0x0a, + 0x02, 0x00, 0x50, 0xe3, 0x0e, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x50, 0xe3, 0x1e, 0xff, 0x2f, 0x11, + 0x5c, 0x10, 0x9f, 0xe5, 0x00, 0x30, 0x91, 0xe5, 0x23, 0x38, 0xa0, 0xe1, 0x03, 0x38, 0x82, 0xe1, + 0x00, 0x30, 0x81, 0xe5, 0x1e, 0xff, 0x2f, 0xe1, 0x44, 0x10, 0x9f, 0xe5, 0xc0, 0x30, 0x91, 0xe5, + 0x03, 0x38, 0xa0, 0xe1, 0x23, 0x38, 0xa0, 0xe1, 0x02, 0x38, 0x83, 0xe1, 0xc0, 0x30, 0x81, 0xe5, + 0x1e, 0xff, 0x2f, 0xe1, 0x28, 0x10, 0x9f, 0xe5, 0xc0, 0x31, 0x91, 0xe5, 0xff, 0x34, 0xc3, 0xe3, + 0x02, 0x3c, 0x83, 0xe1, 0xc0, 0x31, 0x81, 0xe5, 0xe0, 0x31, 0x91, 0xe5, 0xff, 0x30, 0xc3, 0xe3, + 0x22, 0x34, 0x83, 0xe1, 0xe0, 0x31, 0x81, 0xe5, 0x1e, 0xff, 0x2f, 0xe1, 0x22, 0x22, 0x00, 0x00, + 0xef, 0xee, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3c, 0x01, 0x00, 0x50, 0xe3, 0x01, 0x10, 0x21, 0xe2, + 0x06, 0x00, 0x00, 0x0a, 0x02, 0x00, 0x50, 0xe3, 0x09, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x50, 0xe3, + 0x24, 0x30, 0x9f, 0x05, 0x0e, 0x10, 0x81, 0x03, 0x00, 0x12, 0x83, 0x05, 0x1e, 0xff, 0x2f, 0xe1, + 0x18, 0x30, 0x9f, 0xe5, 0x10, 0x20, 0x9f, 0xe5, 0x03, 0x30, 0x81, 0xe1, 0x00, 0x32, 0x82, 0xe5, + 0x1e, 0xff, 0x2f, 0xe1, 0x08, 0x30, 0x9f, 0xe5, 0xf9, 0xff, 0xff, 0xea, 0x00, 0x00, 0xf0, 0x3c, + 0x0e, 0x04, 0x06, 0x00, 0x0e, 0x06, 0x0e, 0x00, 0x02, 0x00, 0x50, 0xe3, 0x10, 0x40, 0x2d, 0xe9, + 0x00, 0x40, 0xa0, 0xe1, 0x30, 0x00, 0xa0, 0x03, 0x02, 0x00, 0x00, 0x0a, 0x01, 0x00, 0x54, 0xe3, + 0x22, 0x00, 0xa0, 0x13, 0x2b, 0x00, 0xa0, 0x03, 0x01, 0x10, 0xa0, 0xe3, 0x3e, 0xff, 0xff, 0xeb, + 0x02, 0x00, 0x54, 0xe3, 0x98, 0x30, 0x9f, 0x05, 0x0f, 0x20, 0xa0, 0xe3, 0x02, 0x00, 0x00, 0x0a, + 0x01, 0x00, 0x54, 0xe3, 0x8c, 0x30, 0x9f, 0x05, 0x8c, 0x30, 0x9f, 0x15, 0x08, 0x20, 0x83, 0xe5, + 0x00, 0x20, 0x93, 0xe5, 0x02, 0x00, 0x54, 0xe3, 0x0c, 0x20, 0x82, 0xe3, 0x00, 0x20, 0x83, 0xe5, + 0x78, 0x30, 0x9f, 0xe5, 0x78, 0x20, 0x9f, 0xe5, 0x04, 0x11, 0x93, 0xe7, 0x11, 0x00, 0x00, 0x1a, + 0x5c, 0x30, 0x9f, 0xe5, 0x30, 0x10, 0x83, 0xe5, 0x06, 0x10, 0xa0, 0xe3, 0x0c, 0x10, 0x83, 0xe5, + 0x04, 0x20, 0x83, 0xe5, 0x00, 0x20, 0x93, 0xe5, 0x02, 0x00, 0x54, 0xe3, 0x0c, 0x20, 0x82, 0xe3, + 0x00, 0x20, 0x83, 0xe5, 0x38, 0x30, 0x9f, 0x05, 0x03, 0x00, 0x00, 0x0a, 0x34, 0x20, 0x9f, 0xe5, + 0x01, 0x00, 0x54, 0xe3, 0x30, 0x30, 0x9f, 0xe5, 0x02, 0x30, 0xa0, 0x01, 0x01, 0x20, 0xa0, 0xe3, + 0x00, 0x20, 0x83, 0xe5, 0x10, 0x80, 0xbd, 0xe8, 0x01, 0x00, 0x54, 0xe3, 0x14, 0x30, 0x9f, 0x05, + 0x14, 0x30, 0x9f, 0x15, 0x06, 0x00, 0xa0, 0xe3, 0x30, 0x10, 0x83, 0xe5, 0x0c, 0x00, 0x83, 0xe5, + 0xea, 0xff, 0xff, 0xea, 0x00, 0x00, 0x20, 0x3d, 0x00, 0x00, 0xe0, 0x3c, 0x00, 0x00, 0x30, 0x3c, + 0xf8, 0x15, 0x00, 0x22, 0x18, 0x06, 0x01, 0x00, 0x02, 0x00, 0x50, 0xe3, 0x30, 0x00, 0xa0, 0x03, + 0x02, 0x00, 0x00, 0x0a, 0x01, 0x00, 0x50, 0xe3, 0x22, 0x00, 0xa0, 0x13, 0x2b, 0x00, 0xa0, 0x03, + 0x00, 0x10, 0xa0, 0xe3, 0x08, 0xff, 0xff, 0xea, 0x02, 0x00, 0x50, 0xe3, 0x90, 0x30, 0x9f, 0x05, + 0x10, 0x40, 0x2d, 0xe9, 0x03, 0x00, 0x00, 0x0a, 0x88, 0x20, 0x9f, 0xe5, 0x01, 0x00, 0x50, 0xe3, + 0x84, 0x30, 0x9f, 0xe5, 0x02, 0x30, 0xa0, 0x01, 0x01, 0x20, 0xa0, 0xe3, 0x00, 0x20, 0x83, 0xe5, + 0x78, 0x20, 0x9f, 0xe5, 0x02, 0x00, 0x50, 0xe3, 0x74, 0x30, 0x9f, 0x05, 0x02, 0x00, 0x00, 0x0a, + 0x01, 0x00, 0x50, 0xe3, 0x6c, 0x30, 0x9f, 0xe5, 0x02, 0x30, 0xa0, 0x01, 0x00, 0x30, 0x93, 0xe5, + 0x1f, 0x3e, 0x03, 0xe2, 0x01, 0x0c, 0x53, 0xe3, 0xf5, 0xff, 0xff, 0x0a, 0x02, 0x00, 0x50, 0xe3, + 0x54, 0x30, 0x9f, 0x05, 0x03, 0x00, 0x00, 0x0a, 0x50, 0x20, 0x9f, 0xe5, 0x01, 0x00, 0x50, 0xe3, + 0x4c, 0x30, 0x9f, 0xe5, 0x02, 0x30, 0xa0, 0x01, 0x00, 0x10, 0x83, 0xe5, 0x57, 0xff, 0xff, 0xeb, + 0x02, 0x00, 0x50, 0xe3, 0x3c, 0x30, 0x9f, 0x05, 0x03, 0x00, 0x00, 0x0a, 0x38, 0x20, 0x9f, 0xe5, + 0x01, 0x00, 0x50, 0xe3, 0x34, 0x30, 0x9f, 0xe5, 0x02, 0x30, 0xa0, 0x01, 0x00, 0x00, 0x93, 0xe5, + 0x10, 0x80, 0xbd, 0xe8, 0x34, 0x00, 0x20, 0x3d, 0x34, 0x00, 0xe0, 0x3c, 0x34, 0x00, 0x30, 0x3c, + 0x08, 0x00, 0xe0, 0x3c, 0x08, 0x00, 0x20, 0x3d, 0x08, 0x00, 0x30, 0x3c, 0x10, 0x00, 0x20, 0x3d, + 0x10, 0x00, 0xe0, 0x3c, 0x10, 0x00, 0x30, 0x3c, 0x20, 0x00, 0x20, 0x3d, 0x20, 0x00, 0xe0, 0x3c, + 0x20, 0x00, 0x30, 0x3c, 0x02, 0x00, 0x50, 0xe3, 0x70, 0x40, 0x2d, 0xe9, 0x02, 0x40, 0xa0, 0xe1, + 0x11, 0x00, 0x00, 0x1a, 0xb0, 0x30, 0x9f, 0xe5, 0xb0, 0x20, 0x9f, 0xe5, 0x34, 0x10, 0x83, 0xe5, + 0x04, 0x30, 0x93, 0xe5, 0xa8, 0x50, 0x9f, 0xe5, 0x01, 0x30, 0x83, 0xe3, 0x01, 0x10, 0x84, 0xe0, + 0x00, 0x30, 0x82, 0xe5, 0x01, 0x00, 0x54, 0xe1, 0x11, 0x00, 0x00, 0x1a, 0x02, 0x00, 0x50, 0xe3, + 0x19, 0x00, 0x00, 0x1a, 0x80, 0x30, 0x9f, 0xe5, 0x80, 0x20, 0x9f, 0xe5, 0x04, 0x30, 0x93, 0xe5, + 0x01, 0x30, 0xc3, 0xe3, 0x00, 0x30, 0x82, 0xe5, 0x70, 0x80, 0xbd, 0xe8, 0x01, 0x00, 0x50, 0xe3, + 0x70, 0x30, 0x9f, 0x05, 0x70, 0x30, 0x9f, 0x15, 0x34, 0x10, 0x83, 0x05, 0x34, 0x10, 0x83, 0x15, + 0x04, 0x30, 0x93, 0x05, 0x64, 0x20, 0x9f, 0x05, 0x04, 0x30, 0x93, 0x15, 0x60, 0x20, 0x9f, 0x15, + 0xe7, 0xff, 0xff, 0xea, 0x21, 0xff, 0xff, 0xeb, 0x02, 0x00, 0x50, 0xe3, 0x54, 0x30, 0x9f, 0x05, + 0x02, 0x00, 0x00, 0x0a, 0x01, 0x00, 0x50, 0xe3, 0x4c, 0x30, 0x9f, 0xe5, 0x05, 0x30, 0xa0, 0x01, + 0x00, 0x30, 0x93, 0xe5, 0x01, 0x30, 0xc4, 0xe4, 0xe1, 0xff, 0xff, 0xea, 0x01, 0x00, 0x50, 0xe3, + 0x20, 0x30, 0x9f, 0x05, 0x20, 0x30, 0x9f, 0x15, 0x04, 0x30, 0x93, 0x05, 0x1c, 0x20, 0x9f, 0x05, + 0x04, 0x30, 0x93, 0x15, 0x18, 0x20, 0x9f, 0x15, 0xe0, 0xff, 0xff, 0xea, 0x00, 0x00, 0x20, 0x3d, + 0x04, 0x00, 0x20, 0x3d, 0x20, 0x00, 0xe0, 0x3c, 0x00, 0x00, 0xe0, 0x3c, 0x00, 0x00, 0x30, 0x3c, + 0x04, 0x00, 0xe0, 0x3c, 0x04, 0x00, 0x30, 0x3c, 0x20, 0x00, 0x20, 0x3d, 0x20, 0x00, 0x30, 0x3c, + 0x01, 0x20, 0xd0, 0xe5, 0x00, 0x30, 0xd0, 0xe5, 0x02, 0x34, 0x83, 0xe1, 0x02, 0x20, 0xd0, 0xe5, + 0x03, 0x00, 0xd0, 0xe5, 0x02, 0x38, 0x83, 0xe1, 0x00, 0x0c, 0x83, 0xe1, 0x1e, 0xff, 0x2f, 0xe1, + 0x10, 0x40, 0x2d, 0xe9, 0x00, 0x40, 0xa0, 0xe1, 0x01, 0x10, 0xa0, 0xe3, 0x04, 0x00, 0xa0, 0xe1, + 0x30, 0xff, 0xff, 0xeb, 0x05, 0x10, 0xa0, 0xe3, 0x04, 0x00, 0xa0, 0xe1, 0x81, 0xff, 0xff, 0xeb, + 0xff, 0x10, 0xa0, 0xe3, 0x04, 0x00, 0xa0, 0xe1, 0x7e, 0xff, 0xff, 0xeb, 0x01, 0x10, 0x10, 0xe2, + 0x03, 0x00, 0x00, 0x0a, 0x00, 0x10, 0xa0, 0xe3, 0x04, 0x00, 0xa0, 0xe1, 0x25, 0xff, 0xff, 0xeb, + 0xf0, 0xff, 0xff, 0xea, 0x04, 0x00, 0xa0, 0xe1, 0x10, 0x40, 0xbd, 0xe8, 0x21, 0xff, 0xff, 0xea, + 0x70, 0x40, 0x2d, 0xe9, 0x00, 0x50, 0x51, 0xe2, 0x00, 0x40, 0xa0, 0xe1, 0x07, 0x00, 0x00, 0x1a, + 0x01, 0x10, 0xa0, 0xe3, 0x1b, 0xff, 0xff, 0xeb, 0x04, 0x10, 0xa0, 0xe3, 0x04, 0x00, 0xa0, 0xe1, + 0x6c, 0xff, 0xff, 0xeb, 0x05, 0x10, 0xa0, 0xe1, 0x04, 0x00, 0xa0, 0xe1, 0x15, 0xff, 0xff, 0xeb, + 0x01, 0x10, 0xa0, 0xe3, 0x04, 0x00, 0xa0, 0xe1, 0x12, 0xff, 0xff, 0xeb, 0x50, 0x10, 0xa0, 0xe3, + 0x04, 0x00, 0xa0, 0xe1, 0x63, 0xff, 0xff, 0xeb, 0x00, 0x10, 0xa0, 0xe3, 0x04, 0x00, 0xa0, 0xe1, + 0x0c, 0xff, 0xff, 0xeb, 0x01, 0x10, 0xa0, 0xe3, 0x04, 0x00, 0xa0, 0xe1, 0x09, 0xff, 0xff, 0xeb, + 0x01, 0x10, 0xa0, 0xe3, 0x04, 0x00, 0xa0, 0xe1, 0x5a, 0xff, 0xff, 0xeb, 0x00, 0x00, 0x55, 0xe3, + 0x1c, 0x10, 0xa0, 0x03, 0x00, 0x10, 0xa0, 0x13, 0x04, 0x00, 0xa0, 0xe1, 0x55, 0xff, 0xff, 0xeb, + 0x00, 0x10, 0xa0, 0xe3, 0x04, 0x00, 0xa0, 0xe1, 0xfe, 0xfe, 0xff, 0xeb, 0x00, 0x00, 0x55, 0xe3, + 0x70, 0x80, 0xbd, 0x08, 0x04, 0x00, 0xa0, 0xe1, 0x01, 0x10, 0xa0, 0xe3, 0xf9, 0xfe, 0xff, 0xeb, + 0x06, 0x10, 0xa0, 0xe3, 0x04, 0x00, 0xa0, 0xe1, 0x4a, 0xff, 0xff, 0xeb, 0x04, 0x00, 0xa0, 0xe1, + 0x00, 0x10, 0xa0, 0xe3, 0x70, 0x40, 0xbd, 0xe8, 0xf2, 0xfe, 0xff, 0xea, 0x70, 0x40, 0x2d, 0xe9, + 0x00, 0x40, 0xa0, 0xe1, 0x01, 0x50, 0xa0, 0xe1, 0x02, 0xff, 0xff, 0xeb, 0x04, 0x00, 0xa0, 0xe1, + 0xb6, 0xff, 0xff, 0xeb, 0x04, 0x00, 0xa0, 0xe1, 0x01, 0x10, 0xa0, 0xe3, 0xc7, 0xff, 0xff, 0xeb, + 0x04, 0x00, 0xa0, 0xe1, 0x01, 0x10, 0xa0, 0xe3, 0xe6, 0xfe, 0xff, 0xeb, 0x20, 0x10, 0xa0, 0xe3, + 0x04, 0x00, 0xa0, 0xe1, 0x37, 0xff, 0xff, 0xeb, 0x25, 0x18, 0xa0, 0xe1, 0xff, 0x10, 0x01, 0xe2, + 0x04, 0x00, 0xa0, 0xe1, 0x33, 0xff, 0xff, 0xeb, 0x25, 0x14, 0xa0, 0xe1, 0xff, 0x10, 0x01, 0xe2, + 0x04, 0x00, 0xa0, 0xe1, 0x2f, 0xff, 0xff, 0xeb, 0x00, 0x10, 0xa0, 0xe3, 0x04, 0x00, 0xa0, 0xe1, + 0x2c, 0xff, 0xff, 0xeb, 0x04, 0x00, 0xa0, 0xe1, 0x00, 0x10, 0xa0, 0xe3, 0xd5, 0xfe, 0xff, 0xeb, + 0x04, 0x00, 0xa0, 0xe1, 0x00, 0x10, 0xa0, 0xe3, 0xb0, 0xff, 0xff, 0xeb, 0x04, 0x00, 0xa0, 0xe1, + 0x70, 0x40, 0xbd, 0xe8, 0x1b, 0xff, 0xff, 0xea, 0x10, 0x40, 0x2d, 0xe9, 0x01, 0x10, 0xa0, 0xe3, + 0x00, 0x40, 0xa0, 0xe1, 0xa7, 0xfe, 0xff, 0xeb, 0x04, 0x00, 0xa0, 0xe1, 0x00, 0x10, 0xa0, 0xe3, + 0x10, 0x40, 0xbd, 0xe8, 0xc7, 0xfe, 0xff, 0xea, 0x00, 0x10, 0xa0, 0xe3, 0xa1, 0xfe, 0xff, 0xea, + 0xf0, 0x41, 0x2d, 0xe9, 0x00, 0x40, 0xa0, 0xe1, 0x03, 0x70, 0xa0, 0xe1, 0x02, 0x60, 0xa0, 0xe1, + 0x01, 0x50, 0xa0, 0xe1, 0xd3, 0xfe, 0xff, 0xeb, 0x04, 0x00, 0xa0, 0xe1, 0x87, 0xff, 0xff, 0xeb, + 0x04, 0x00, 0xa0, 0xe1, 0x01, 0x10, 0xa0, 0xe3, 0xba, 0xfe, 0xff, 0xeb, 0x03, 0x10, 0xa0, 0xe3, + 0x04, 0x00, 0xa0, 0xe1, 0x0b, 0xff, 0xff, 0xeb, 0x25, 0x18, 0xa0, 0xe1, 0xff, 0x10, 0x01, 0xe2, + 0x04, 0x00, 0xa0, 0xe1, 0x07, 0xff, 0xff, 0xeb, 0x25, 0x14, 0xa0, 0xe1, 0xff, 0x10, 0x01, 0xe2, + 0x04, 0x00, 0xa0, 0xe1, 0x03, 0xff, 0xff, 0xeb, 0xff, 0x10, 0x05, 0xe2, 0x04, 0x00, 0xa0, 0xe1, + 0x00, 0xff, 0xff, 0xeb, 0x07, 0x20, 0xa0, 0xe1, 0x06, 0x10, 0xa0, 0xe1, 0x04, 0x00, 0xa0, 0xe1, + 0x2f, 0xff, 0xff, 0xeb, 0x04, 0x00, 0xa0, 0xe1, 0x00, 0x10, 0xa0, 0xe3, 0xa5, 0xfe, 0xff, 0xeb, + 0x04, 0x00, 0xa0, 0xe1, 0xf0, 0x41, 0xbd, 0xe8, 0xee, 0xfe, 0xff, 0xea, 0x70, 0x40, 0x2d, 0xe9, + 0x00, 0x60, 0xa0, 0xe1, 0x02, 0x40, 0xa0, 0xe1, 0x01, 0x56, 0xa0, 0xe1, 0x00, 0x00, 0x54, 0xe3, + 0x70, 0x80, 0xbd, 0x08, 0x05, 0x10, 0xa0, 0xe1, 0x06, 0x00, 0xa0, 0xe1, 0xa6, 0xff, 0xff, 0xeb, + 0x01, 0x40, 0x44, 0xe2, 0x01, 0x5a, 0x85, 0xe2, 0xf7, 0xff, 0xff, 0xea, 0xf0, 0x47, 0x2d, 0xe9, + 0x00, 0x40, 0xa0, 0xe1, 0x20, 0xd0, 0x4d, 0xe2, 0x01, 0x60, 0xa0, 0xe1, 0x03, 0x50, 0xa0, 0xe1, + 0x03, 0x70, 0x82, 0xe0, 0xa3, 0xfe, 0xff, 0xeb, 0x04, 0x00, 0xa0, 0xe1, 0x57, 0xff, 0xff, 0xeb, + 0x01, 0x10, 0xa0, 0xe3, 0x04, 0x00, 0xa0, 0xe1, 0x8a, 0xfe, 0xff, 0xeb, 0x03, 0x10, 0xa0, 0xe3, + 0x04, 0x00, 0xa0, 0xe1, 0xdb, 0xfe, 0xff, 0xeb, 0x46, 0x18, 0xa0, 0xe1, 0xff, 0x10, 0x01, 0xe2, + 0x04, 0x00, 0xa0, 0xe1, 0xd7, 0xfe, 0xff, 0xeb, 0x46, 0x14, 0xa0, 0xe1, 0xff, 0x10, 0x01, 0xe2, + 0x04, 0x00, 0xa0, 0xe1, 0xd3, 0xfe, 0xff, 0xeb, 0xff, 0x10, 0x06, 0xe2, 0x04, 0x00, 0xa0, 0xe1, + 0xd0, 0xfe, 0xff, 0xeb, 0x00, 0x60, 0xa0, 0xe3, 0x00, 0x00, 0x55, 0xe3, 0x07, 0x00, 0x00, 0xca, + 0x00, 0x10, 0xa0, 0xe3, 0x04, 0x00, 0xa0, 0xe1, 0x76, 0xfe, 0xff, 0xeb, 0x04, 0x00, 0xa0, 0xe1, + 0xc0, 0xfe, 0xff, 0xeb, 0x06, 0x00, 0xa0, 0xe1, 0x20, 0xd0, 0x8d, 0xe2, 0xf0, 0x87, 0xbd, 0xe8, + 0x20, 0x00, 0x55, 0xe3, 0x05, 0x90, 0xa0, 0xb1, 0x20, 0x90, 0xa0, 0xa3, 0x0d, 0x20, 0xa0, 0xe1, + 0x09, 0x10, 0xa0, 0xe1, 0x04, 0x00, 0xa0, 0xe1, 0xf1, 0xfe, 0xff, 0xeb, 0x09, 0x20, 0xa0, 0xe1, + 0x0d, 0x10, 0xa0, 0xe1, 0x05, 0x00, 0x47, 0xe0, 0xb5, 0xfd, 0xff, 0xeb, 0x00, 0x30, 0xa0, 0xe3, + 0x00, 0x00, 0x50, 0xe3, 0x01, 0x60, 0x86, 0x13, 0x03, 0x20, 0xdd, 0xe7, 0x02, 0x30, 0x83, 0xe2, + 0xff, 0x00, 0x52, 0xe3, 0x02, 0x60, 0x86, 0x13, 0x03, 0x00, 0x59, 0xe1, 0xf9, 0xff, 0xff, 0xca, + 0x20, 0x50, 0x45, 0xe2, 0xdf, 0xff, 0xff, 0xea, 0xf7, 0x4f, 0x2d, 0xe9, 0x00, 0x40, 0xa0, 0xe1, + 0x01, 0x70, 0xa0, 0xe1, 0x02, 0x50, 0xa0, 0xe1, 0x03, 0x80, 0xa0, 0xe1, 0x00, 0x00, 0x58, 0xe3, + 0x01, 0x00, 0x00, 0xca, 0x0c, 0xd0, 0x8d, 0xe2, 0xf0, 0x8f, 0xbd, 0xe8, 0x07, 0x6a, 0xa0, 0xe1, + 0x26, 0x6a, 0xa0, 0xe1, 0x01, 0x6a, 0x66, 0xe2, 0x08, 0x00, 0x56, 0xe1, 0x08, 0x60, 0xa0, 0xa1, + 0x06, 0x30, 0xa0, 0xe1, 0x05, 0x20, 0xa0, 0xe1, 0x07, 0x10, 0xa0, 0xe1, 0x04, 0x00, 0xa0, 0xe1, + 0xb1, 0xff, 0xff, 0xeb, 0x06, 0xa0, 0x85, 0xe0, 0x01, 0x00, 0x10, 0xe3, 0x0d, 0x00, 0x00, 0x0a, + 0x02, 0x00, 0x10, 0xe3, 0x03, 0x00, 0x00, 0x0a, 0xff, 0x1e, 0xc7, 0xe3, 0x0f, 0x10, 0xc1, 0xe3, + 0x04, 0x00, 0xa0, 0xe1, 0x4c, 0xff, 0xff, 0xeb, 0x06, 0x90, 0xa0, 0xe1, 0x05, 0x30, 0xa0, 0xe1, + 0x00, 0x20, 0xd3, 0xe5, 0xff, 0x00, 0x52, 0xe3, 0x06, 0x00, 0x00, 0x1a, 0x01, 0x30, 0x83, 0xe2, + 0x0a, 0x00, 0x53, 0xe1, 0xf9, 0xff, 0xff, 0x1a, 0x0a, 0x50, 0xa0, 0xe1, 0x06, 0x70, 0x87, 0xe0, + 0x06, 0x80, 0x48, 0xe0, 0xdc, 0xff, 0xff, 0xea, 0x04, 0x00, 0xa0, 0xe1, 0x45, 0xfe, 0xff, 0xeb, + 0x04, 0x00, 0xa0, 0xe1, 0xf9, 0xfe, 0xff, 0xeb, 0x27, 0xb8, 0xa0, 0xe1, 0x01, 0x10, 0xa0, 0xe3, + 0x04, 0x00, 0xa0, 0xe1, 0x09, 0xff, 0xff, 0xeb, 0x02, 0x50, 0x85, 0xe2, 0x01, 0x30, 0xa0, 0xe3, + 0xff, 0xb0, 0x0b, 0xe2, 0x01, 0x10, 0xa0, 0xe3, 0x04, 0x00, 0xa0, 0xe1, 0x04, 0x30, 0x8d, 0xe5, + 0x24, 0xfe, 0xff, 0xeb, 0xad, 0x10, 0xa0, 0xe3, 0x04, 0x00, 0xa0, 0xe1, 0x75, 0xfe, 0xff, 0xeb, + 0x04, 0x30, 0x9d, 0xe5, 0x00, 0x00, 0x53, 0xe3, 0x09, 0x00, 0x00, 0x0a, 0x0b, 0x10, 0xa0, 0xe1, + 0x04, 0x00, 0xa0, 0xe1, 0x6f, 0xfe, 0xff, 0xeb, 0x27, 0x14, 0xa0, 0xe1, 0xff, 0x10, 0x01, 0xe2, + 0x04, 0x00, 0xa0, 0xe1, 0x6b, 0xfe, 0xff, 0xeb, 0xff, 0x10, 0x07, 0xe2, 0x04, 0x00, 0xa0, 0xe1, + 0x68, 0xfe, 0xff, 0xeb, 0x02, 0x10, 0x55, 0xe5, 0x04, 0x00, 0xa0, 0xe1, 0x65, 0xfe, 0xff, 0xeb, + 0x01, 0x10, 0x55, 0xe5, 0x04, 0x00, 0xa0, 0xe1, 0x62, 0xfe, 0xff, 0xeb, 0x00, 0x10, 0xa0, 0xe3, + 0x04, 0x00, 0xa0, 0xe1, 0x0b, 0xfe, 0xff, 0xeb, 0x04, 0x00, 0xa0, 0xe1, 0xd3, 0xfe, 0xff, 0xeb, + 0x02, 0x90, 0x59, 0xe2, 0x02, 0x50, 0x85, 0xe2, 0x00, 0x30, 0xa0, 0xe3, 0xdc, 0xff, 0xff, 0x1a, + 0x04, 0x00, 0xa0, 0xe1, 0x09, 0x10, 0xa0, 0xe1, 0xe0, 0xfe, 0xff, 0xeb, 0x04, 0x00, 0xa0, 0xe1, + 0x4c, 0xfe, 0xff, 0xeb, 0xc7, 0xff, 0xff, 0xea, 0x10, 0x40, 0x2d, 0xe9, 0x0c, 0x00, 0x80, 0xe2, + 0xbe, 0xfe, 0xff, 0xeb, 0x5f, 0x0d, 0x80, 0xe2, 0x3f, 0x00, 0x80, 0xe2, 0xff, 0x0e, 0xc0, 0xe3, + 0x0f, 0x00, 0xc0, 0xe3, 0x10, 0x80, 0xbd, 0xe8, 0xff, 0x41, 0x2d, 0xe9, 0x01, 0x50, 0xa0, 0xe1, + 0x00, 0x80, 0xa0, 0xe1, 0x00, 0x00, 0xa0, 0xe3, 0x02, 0x60, 0xa0, 0xe1, 0x21, 0xff, 0xff, 0xeb, + 0x05, 0x30, 0xa0, 0xe1, 0x02, 0x2b, 0xa0, 0xe3, 0x08, 0x10, 0xa0, 0xe1, 0x00, 0x00, 0xa0, 0xe3, + 0x26, 0xff, 0xff, 0xeb, 0x00, 0x00, 0xa0, 0xe3, 0x22, 0xff, 0xff, 0xeb, 0x08, 0x11, 0x9f, 0xe5, + 0x04, 0x20, 0xa0, 0xe3, 0x05, 0x00, 0xa0, 0xe1, 0x35, 0xfd, 0xff, 0xeb, 0x00, 0x00, 0x50, 0xe3, + 0x00, 0x40, 0xe0, 0x13, 0x37, 0x00, 0x00, 0x1a, 0x0d, 0x30, 0xa0, 0xe1, 0x40, 0x20, 0xa0, 0xe3, + 0x05, 0x10, 0xa0, 0xe1, 0x02, 0x00, 0xa0, 0xe3, 0xe8, 0x00, 0x00, 0xeb, 0x10, 0x20, 0xa0, 0xe3, + 0x40, 0x10, 0x85, 0xe2, 0x0d, 0x00, 0xa0, 0xe1, 0x29, 0xfd, 0xff, 0xeb, 0x00, 0x40, 0x50, 0xe2, + 0x01, 0x40, 0xe0, 0x13, 0x2b, 0x00, 0x00, 0x1a, 0x0c, 0x00, 0x85, 0xe2, 0x97, 0xfe, 0xff, 0xeb, + 0x7e, 0x0b, 0x50, 0xe3, 0x00, 0x70, 0xa0, 0xe1, 0x29, 0x00, 0x00, 0x8a, 0x08, 0x00, 0x85, 0xe2, + 0x92, 0xfe, 0xff, 0xeb, 0x07, 0x00, 0x50, 0xe1, 0x25, 0x00, 0x00, 0x8a, 0x07, 0x30, 0xd5, 0xe5, + 0x01, 0x30, 0x43, 0xe2, 0xff, 0x30, 0x03, 0xe2, 0x01, 0x00, 0x53, 0xe3, 0x03, 0x40, 0xe0, 0x83, + 0x1c, 0x00, 0x00, 0x8a, 0x00, 0x00, 0x56, 0xe3, 0x1a, 0x00, 0x00, 0x0a, 0x04, 0x00, 0xa0, 0xe1, + 0xf4, 0xfe, 0xff, 0xeb, 0x06, 0x30, 0xa0, 0xe1, 0x07, 0x20, 0xa0, 0xe1, 0x02, 0x1b, 0x88, 0xe2, + 0x04, 0x00, 0xa0, 0xe1, 0xf9, 0xfe, 0xff, 0xeb, 0x04, 0x00, 0xa0, 0xe1, 0xf5, 0xfe, 0xff, 0xeb, + 0x07, 0x30, 0xd5, 0xe5, 0x01, 0x00, 0x53, 0xe3, 0x03, 0x00, 0x00, 0x1a, 0x06, 0x20, 0xa0, 0xe1, + 0x05, 0x10, 0xa0, 0xe1, 0x04, 0x00, 0xa0, 0xe1, 0xd3, 0x00, 0x00, 0xeb, 0x0d, 0x30, 0xa0, 0xe1, + 0x07, 0x20, 0xa0, 0xe1, 0x06, 0x10, 0xa0, 0xe1, 0x02, 0x00, 0xa0, 0xe3, 0xbb, 0x00, 0x00, 0xeb, + 0x10, 0x20, 0xa0, 0xe3, 0x02, 0x10, 0x85, 0xe0, 0x0d, 0x00, 0xa0, 0xe1, 0xfc, 0xfc, 0xff, 0xeb, + 0x00, 0x00, 0x50, 0xe3, 0x04, 0x40, 0xe0, 0x13, 0x04, 0x00, 0xa0, 0xe1, 0x10, 0xd0, 0x8d, 0xe2, + 0xf0, 0x81, 0xbd, 0xe8, 0x02, 0x40, 0xe0, 0xe3, 0xfa, 0xff, 0xff, 0xea, 0x18, 0x16, 0x00, 0x22, + 0x70, 0x40, 0x2d, 0xe9, 0x00, 0x60, 0xa0, 0xe1, 0x0c, 0x00, 0x81, 0xe2, 0x63, 0xfe, 0xff, 0xeb, + 0x01, 0x50, 0xa0, 0xe1, 0x02, 0x4b, 0x80, 0xe2, 0x00, 0x00, 0xa0, 0xe3, 0xcd, 0xfe, 0xff, 0xeb, + 0x04, 0x30, 0xa0, 0xe1, 0x05, 0x20, 0xa0, 0xe1, 0x06, 0x10, 0xa0, 0xe1, 0x00, 0x00, 0xa0, 0xe3, + 0x3c, 0xff, 0xff, 0xeb, 0x04, 0x30, 0xa0, 0xe1, 0x05, 0x20, 0xa0, 0xe1, 0x06, 0x10, 0xa0, 0xe1, + 0x00, 0x00, 0xa0, 0xe3, 0xfc, 0xfe, 0xff, 0xeb, 0x00, 0x40, 0xa0, 0xe1, 0x00, 0x00, 0xa0, 0xe3, + 0xc8, 0xfe, 0xff, 0xeb, 0x04, 0x00, 0xe0, 0xe1, 0x01, 0x00, 0x00, 0xe2, 0x70, 0x80, 0xbd, 0xe8, + 0xf0, 0x47, 0x2d, 0xe9, 0x00, 0x80, 0xa0, 0xe1, 0x01, 0x90, 0xa0, 0xe1, 0x0a, 0x00, 0xa0, 0xe3, + 0x01, 0x10, 0xa0, 0xe3, 0xe3, 0x45, 0xa0, 0xe3, 0x03, 0x50, 0xa0, 0xe1, 0x02, 0x60, 0xa0, 0xe1, + 0x01, 0x70, 0xa0, 0xe3, 0xe0, 0xfc, 0xff, 0xeb, 0x00, 0x00, 0xa0, 0xe3, 0x74, 0x00, 0x84, 0xe5, + 0x78, 0x00, 0x84, 0xe5, 0x7c, 0x00, 0x84, 0xe5, 0x80, 0x00, 0x84, 0xe5, 0x08, 0x70, 0x84, 0xe5, + 0x08, 0x00, 0x84, 0xe5, 0x00, 0x70, 0x84, 0xe5, 0x10, 0x00, 0x84, 0xe5, 0x6c, 0x90, 0x84, 0xe5, + 0x6c, 0x30, 0x94, 0xe5, 0x07, 0x00, 0x58, 0xe1, 0x03, 0x30, 0xe0, 0xe1, 0x88, 0x30, 0x84, 0xe5, + 0x08, 0x30, 0xa0, 0x13, 0x09, 0x30, 0xa0, 0x03, 0x8c, 0x00, 0x84, 0xe5, 0x14, 0x30, 0x84, 0xe5, + 0x18, 0x50, 0x84, 0xe5, 0x20, 0x60, 0x84, 0xe5, 0x24, 0x50, 0x84, 0xe5, 0x28, 0x60, 0x84, 0xe5, + 0x2c, 0x50, 0x84, 0xe5, 0x30, 0x60, 0x84, 0xe5, 0x34, 0x50, 0x84, 0xe5, 0xde, 0x00, 0x00, 0xeb, + 0x04, 0x70, 0x84, 0xe5, 0x0c, 0x30, 0x94, 0xe5, 0x0f, 0x00, 0x13, 0xe3, 0xfc, 0xff, 0xff, 0x0a, + 0xf0, 0x47, 0xbd, 0xe8, 0x00, 0x10, 0xa0, 0xe3, 0x0a, 0x00, 0xa0, 0xe3, 0xbe, 0xfc, 0xff, 0xea, + 0xf0, 0x4f, 0x2d, 0xe9, 0x00, 0x60, 0xa0, 0xe1, 0x54, 0xd0, 0x4d, 0xe2, 0x01, 0x40, 0xa0, 0xe1, + 0x00, 0x00, 0xa0, 0xe3, 0x01, 0x10, 0xa0, 0xe3, 0x02, 0x50, 0xa0, 0xe1, 0xb6, 0xfc, 0xff, 0xeb, + 0x0e, 0x23, 0xa0, 0xe3, 0x01, 0x30, 0xa0, 0xe3, 0x04, 0x30, 0x82, 0xe5, 0x00, 0x30, 0x92, 0xe5, + 0x01, 0x30, 0x13, 0xe2, 0xfc, 0xff, 0xff, 0x1a, 0x04, 0x30, 0x82, 0xe5, 0x00, 0x30, 0x82, 0xe5, + 0x40, 0x20, 0x64, 0xe2, 0x06, 0x20, 0x42, 0xe0, 0x50, 0x71, 0x9f, 0xe5, 0x04, 0x20, 0x8d, 0xe5, + 0x04, 0x20, 0x86, 0xe0, 0x08, 0x20, 0x8d, 0xe5, 0x06, 0x00, 0xa0, 0xe1, 0xa4, 0x2e, 0xa0, 0xe1, + 0xa4, 0x9a, 0xa0, 0xe1, 0xa4, 0xa6, 0xa0, 0xe1, 0x0e, 0x13, 0xa0, 0xe3, 0x0c, 0x20, 0x8d, 0xe5, + 0x04, 0x20, 0x9d, 0xe5, 0x00, 0x60, 0x82, 0xe0, 0x00, 0x00, 0x56, 0xe3, 0x3e, 0x00, 0x00, 0xda, + 0x10, 0x20, 0x8d, 0xe2, 0x02, 0xe0, 0xa0, 0xe1, 0x00, 0xc0, 0xa0, 0xe3, 0x01, 0xc0, 0x8c, 0xe2, + 0x10, 0x00, 0x5c, 0xe3, 0x04, 0x30, 0x8e, 0xe4, 0xfb, 0xff, 0xff, 0x1a, 0x40, 0x00, 0x56, 0xe3, + 0x30, 0x00, 0x00, 0xda, 0x0c, 0xc0, 0x9d, 0xe5, 0x4c, 0x90, 0xcd, 0xe5, 0x4b, 0xc0, 0xcd, 0xe5, + 0xa4, 0xc2, 0xa0, 0xe1, 0x4e, 0xc0, 0xcd, 0xe5, 0x84, 0xc1, 0xa0, 0xe1, 0x4f, 0xc0, 0xcd, 0xe5, + 0x01, 0xc0, 0xa0, 0xe3, 0x4d, 0xa0, 0xcd, 0xe5, 0xd4, 0xe0, 0x9f, 0xe5, 0x04, 0x60, 0x92, 0xe4, + 0x04, 0x60, 0x8e, 0xe4, 0x07, 0x00, 0x5e, 0xe1, 0xfb, 0xff, 0xff, 0x1a, 0x00, 0x20, 0x91, 0xe5, + 0x02, 0x20, 0x82, 0xe3, 0x00, 0x20, 0x81, 0xe5, 0x00, 0x20, 0x91, 0xe5, 0x01, 0x00, 0x12, 0xe3, + 0xfc, 0xff, 0xff, 0x1a, 0x00, 0x20, 0x91, 0xe5, 0x00, 0x00, 0x5c, 0xe3, 0x08, 0x20, 0x82, 0xe3, + 0x00, 0x20, 0x81, 0xe5, 0xd9, 0xff, 0xff, 0x0a, 0x98, 0x30, 0x9f, 0xe5, 0x98, 0x20, 0x9f, 0xe5, + 0x04, 0x10, 0x93, 0xe4, 0x02, 0x00, 0x53, 0xe1, 0x04, 0x10, 0x85, 0xe4, 0xfb, 0xff, 0xff, 0x1a, + 0x00, 0x10, 0xa0, 0xe3, 0x01, 0x00, 0xa0, 0xe1, 0x73, 0xfc, 0xff, 0xeb, 0x54, 0xd0, 0x8d, 0xe2, + 0xf0, 0x8f, 0xbd, 0xe8, 0x0e, 0xb0, 0xd0, 0xe7, 0x01, 0xe0, 0x8e, 0xe2, 0x01, 0xb0, 0xc8, 0xe4, + 0x0e, 0x00, 0x5c, 0xe1, 0xfa, 0xff, 0xff, 0xca, 0x50, 0xc0, 0x8c, 0xe2, 0x0c, 0xc0, 0x8d, 0xe0, + 0x7f, 0xe0, 0xe0, 0xe3, 0x07, 0x00, 0x56, 0xe3, 0x40, 0xe0, 0x4c, 0xe5, 0xd0, 0xff, 0xff, 0xca, + 0x00, 0xc0, 0xa0, 0xe3, 0xd7, 0xff, 0xff, 0xea, 0x08, 0xc0, 0x9d, 0xe5, 0x02, 0x80, 0xa0, 0xe1, + 0x00, 0xe0, 0xa0, 0xe3, 0x00, 0xc0, 0x4c, 0xe0, 0xf0, 0xff, 0xff, 0xea, 0x20, 0x20, 0x9f, 0xe5, + 0x04, 0xc0, 0x40, 0xe2, 0x04, 0xe0, 0xbc, 0xe5, 0x04, 0xe0, 0x82, 0xe4, 0x07, 0x00, 0x52, 0xe1, + 0xfb, 0xff, 0xff, 0x1a, 0x40, 0x00, 0x80, 0xe2, 0x00, 0xc0, 0xa0, 0xe3, 0xce, 0xff, 0xff, 0xea, + 0x80, 0x00, 0x00, 0x38, 0x40, 0x00, 0x00, 0x38, 0x20, 0x00, 0x00, 0x38, 0x34, 0x00, 0x00, 0x38, + 0x30, 0x40, 0x2d, 0xe9, 0x1c, 0xd0, 0x4d, 0xe2, 0x03, 0x40, 0xa0, 0xe1, 0x00, 0x50, 0xa0, 0xe1, + 0x01, 0x00, 0xa0, 0xe1, 0x02, 0x10, 0xa0, 0xe1, 0x04, 0x20, 0x8d, 0xe2, 0x8b, 0xff, 0xff, 0xeb, + 0x04, 0x10, 0x8d, 0xe2, 0x10, 0x20, 0xa0, 0xe3, 0x04, 0x00, 0xa0, 0xe1, 0x57, 0x00, 0x00, 0xeb, + 0x10, 0x30, 0xa0, 0xe3, 0x04, 0x20, 0xa0, 0xe1, 0x05, 0x10, 0xa0, 0xe1, 0x01, 0x00, 0xa0, 0xe3, + 0x56, 0xff, 0xff, 0xeb, 0x1c, 0xd0, 0x8d, 0xe2, 0x30, 0x80, 0xbd, 0xe8, 0x70, 0x40, 0x2d, 0xe9, + 0x0d, 0x30, 0xd1, 0xe5, 0x0c, 0x40, 0xd1, 0xe5, 0x01, 0x00, 0x50, 0xe3, 0x03, 0x44, 0x84, 0xe1, + 0x0e, 0x30, 0xd1, 0xe5, 0x00, 0x50, 0xa0, 0xe1, 0x03, 0x48, 0x84, 0xe1, 0x0f, 0x30, 0xd1, 0xe5, + 0x02, 0x60, 0xa0, 0xe1, 0x03, 0x4c, 0x84, 0xe1, 0x02, 0x30, 0xa0, 0x13, 0x01, 0x30, 0xa0, 0x03, + 0x07, 0x30, 0xc1, 0xe5, 0x40, 0x20, 0xa0, 0xe3, 0x40, 0x30, 0x81, 0xe2, 0x02, 0x00, 0xa0, 0xe3, + 0xda, 0xff, 0xff, 0xeb, 0x04, 0x30, 0xa0, 0xe1, 0x06, 0x20, 0xa0, 0xe1, 0x05, 0x00, 0xa0, 0xe1, + 0x02, 0x10, 0xa0, 0xe3, 0x70, 0x40, 0xbd, 0xe8, 0x3c, 0xff, 0xff, 0xea, 0x1c, 0x20, 0x9f, 0xe5, + 0x00, 0x00, 0x50, 0xe3, 0x00, 0x30, 0x92, 0xe5, 0xee, 0x14, 0xa0, 0x03, 0xff, 0x34, 0xc3, 0xe3, + 0x53, 0x14, 0xa0, 0x13, 0x01, 0x30, 0x83, 0xe1, 0x00, 0x30, 0x82, 0xe5, 0x1e, 0xff, 0x2f, 0xe1, + 0x00, 0x00, 0xf0, 0x3c, 0x70, 0x40, 0x2d, 0xe9, 0x60, 0x40, 0x9f, 0xe5, 0xfa, 0x3f, 0xa0, 0xe3, + 0xb4, 0x50, 0x94, 0xe5, 0x00, 0x60, 0xa0, 0xe1, 0x01, 0x00, 0xa0, 0xe3, 0x93, 0x51, 0x25, 0xe0, + 0xed, 0xff, 0xff, 0xeb, 0x48, 0x10, 0x9f, 0xe5, 0x00, 0x30, 0xa0, 0xe3, 0xb4, 0x20, 0x94, 0xe5, + 0x05, 0x00, 0x52, 0xe1, 0x02, 0x00, 0x00, 0xba, 0x70, 0x40, 0xbd, 0xe8, 0x00, 0x00, 0xa0, 0xe3, + 0xe5, 0xff, 0xff, 0xea, 0x01, 0x20, 0x23, 0xe2, 0x06, 0x3c, 0x23, 0xe2, 0x0f, 0x30, 0x23, 0xe2, + 0x00, 0x32, 0x81, 0xe5, 0xb4, 0x30, 0x94, 0xe5, 0xa6, 0x30, 0x83, 0xe0, 0xb4, 0x00, 0x94, 0xe5, + 0x03, 0x00, 0x50, 0xe1, 0xfc, 0xff, 0xff, 0xba, 0x02, 0x30, 0xa0, 0xe1, 0xee, 0xff, 0xff, 0xea, + 0x00, 0x00, 0x70, 0x3c, 0x00, 0x00, 0xf0, 0x3c, 0x70, 0x40, 0x2d, 0xe9, 0x38, 0x50, 0x9f, 0xe5, + 0x06, 0x40, 0x80, 0xe2, 0xfa, 0x6f, 0xa0, 0xe3, 0xb6, 0x00, 0x54, 0xe1, 0x00, 0x00, 0x50, 0xe3, + 0x70, 0x80, 0xbd, 0x08, 0xb4, 0x10, 0x54, 0xe1, 0xd9, 0xff, 0xff, 0xeb, 0xb4, 0x30, 0x95, 0xe5, + 0xb2, 0x20, 0x54, 0xe1, 0x96, 0x32, 0x23, 0xe0, 0xb4, 0x20, 0x95, 0xe5, 0x03, 0x00, 0x52, 0xe1, + 0xfc, 0xff, 0xff, 0xba, 0x06, 0x40, 0x84, 0xe2, 0xf2, 0xff, 0xff, 0xea, 0x00, 0x00, 0x70, 0x3c, + 0x01, 0x30, 0x40, 0xe2, 0x02, 0x20, 0x81, 0xe0, 0x02, 0x00, 0x51, 0xe1, 0x1e, 0xff, 0x2f, 0x01, + 0x01, 0xc0, 0xd1, 0xe4, 0x01, 0xc0, 0xe3, 0xe5, 0xfa, 0xff, 0xff, 0xea, 0x7e, 0xff, 0x17, 0xee, + 0xfd, 0xff, 0xff, 0x1a, 0x00, 0x10, 0xa0, 0xe3, 0x9a, 0x1f, 0x07, 0xee, 0x1e, 0xff, 0x2f, 0xe1, + 0xc1, 0x3e, 0xbc, 0x68, 0x7c, 0xcd, 0xe8, 0x22, 0xad, 0xbd, 0x88, 0x94, 0xa9, 0x95, 0x21, 0x4b, + 0xc4, 0x57, 0xfd, 0x04, 0x8e, 0xfb, 0xf1, 0x1a, 0x9d, 0xfa, 0x80, 0x11, 0x1e, 0x3f, 0xa6, 0x3e, + 0x2e, 0x7f, 0xa3, 0x4a, 0xf1, 0xaa, 0x75, 0x4b, 0x34, 0xf7, 0xb9, 0xc4, 0x58, 0xcf, 0xbd, 0xdb, + 0x15, 0xa6, 0x6a, 0xbf, 0xa4, 0x3d, 0xfa, 0x76, 0x9f, 0xce, 0xce, 0x39, 0x1f, 0x28, 0xeb, 0x47, + 0x60, 0xac, 0x5a, 0x12, 0x38, 0x65, 0x0d, 0x2b, 0xc6, 0x63, 0x15, 0x02, 0xa0, 0x44, 0x84, 0x39, + 0xb8, 0x0b, 0xf4, 0x01, 0xf4, 0x01, 0xb8, 0x0b, 0xf4, 0x01, 0xf4, 0x01, 0xb8, 0x0b, 0xf4, 0x01, + 0x00, 0x00, 0x00, 0x00, 0xe8, 0x03, 0x64, 0x00, 0x00, 0x00, 0xf4, 0x01, 0x96, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf4, 0x01, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0b, 0x08, 0x05, 0x00, 0x4c, 0x00, 0x50, 0x3c, + 0x58, 0x00, 0x50, 0x3c, 0x68, 0x00, 0x50, 0x3c, 0x6c, 0x00, 0x50, 0x3c, 0x38, 0x37, 0x30, 0x32, + 0x00, 0x00, 0x00, 0x00 +}; diff --git a/utils/mks5lboot/dualboot.h b/utils/mks5lboot/dualboot.h index c8952a4c07..969cadedfc 100644 --- a/utils/mks5lboot/dualboot.h +++ b/utils/mks5lboot/dualboot.h @@ -1,4 +1,6 @@ /* Generated by bin2c */ extern unsigned char dualboot_install_ipod6g[5396]; +extern unsigned char dualboot_install_ipodnano3g[5188]; extern unsigned char dualboot_uninstall_ipod6g[5076]; +extern unsigned char dualboot_uninstall_ipodnano3g[4884]; diff --git a/utils/mks5lboot/dualboot/Makefile b/utils/mks5lboot/dualboot/Makefile index ad0603a216..d17916f819 100644 --- a/utils/mks5lboot/dualboot/Makefile +++ b/utils/mks5lboot/dualboot/Makefile @@ -27,8 +27,9 @@ LINKFILE = dualboot.lds # 1) add x to the list in TARGETS # 2) create a variable named OPT_x of the form: # OPT_x=target specific defines -TARGETS = ipod6g +TARGETS = ipod6g ipodnano3g OPT_ipod6g = -DIPOD_6G -DMEMORYSIZE=64 +OPT_ipodnano3g = -DIPOD_NANO3G -DMEMORYSIZE=32 LOADERS = install uninstall OPT_install = @@ -53,7 +54,15 @@ $(foreach l, $(LOADERS), $(foreach t, $(TARGETS),\ INCLUDES += -I. -I.. -I$(FIRMDIR) -I$(FWARM) -I$(FW8702) INCLUDES += $(addprefix -I$(FIRMDIR)/, export include libc/include kernel/include) # target/loader specific includes -$(foreach l,$(LOADERS),$(foreach t,$(TARGETS),$(eval INC_$(l)_$(t) = -I$(FW8702)/$(t)))) +# +# Each target also needs the autoconf.h that tools/configure generates for +# its bootloader build; it cannot be derived here. Configure a bootloader +# build for each target and pass the directories, e.g. +# +# make CONFIGDIR_ipod6g=../../../build-ipod6g-bl \ +# CONFIGDIR_ipodnano3g=../../../build-nano3g-bl +$(foreach l,$(LOADERS),$(foreach t,$(TARGETS),\ + $(eval INC_$(l)_$(t) = -I$(FW8702)/$(t) -I$(CONFIGDIR_$(t))))) CFLAGS = $(INCLUDES) -mcpu=arm926ej-s -std=gnu99 -nostdlib -ffreestanding -Os -W -Wall\ -Wundef -Wstrict-prototypes -ffunction-sections -fdata-sections -Wl,--gc-sections $(DEFINES) @@ -69,9 +78,14 @@ OUTPUTDEBUG = $(BOOTBINS:%.arm-bin=$(BUILDDIR)%.arm-elf) $(BOOTBINS:%.arm-bin=$( all: $(BUILDDIR) $(OUTPUTDUALBOOT) +# config.h emits ARM assembler macros (ldmpc/ldrpc) whenever __ASSEMBLER__ is +# defined, which the linker script has to do; strip them and their bodies, or +# ld chokes on them. Lines containing a backslash are macro bodies. $(BUILDDIR)$(PFX)%.lds: $(LINKFILE) @echo Creating $@ - $(SILENT)$(CROSS)$(CC) $(INC_$*) $(CFLAGS) $(OPT_$*) -E -x c - < $< | sed '/#/d' > $@ + $(SILENT)$(CROSS)$(CC) $(INC_$*) $(CFLAGS) $(OPT_$*) -E -x c - < $< \ + | sed -e '/#/d' -e '/^[[:space:]]*\.\(macro\|endm\|syntax\)/d' \ + -e '/\\/d' > $@ $(BUILDDIR)$(PFX)%.arm-elf: $(BUILDDIR)$(PFX)%.lds $(SOURCES) @echo CC -T $(notdir $^ $(SRC_$*)) diff --git a/utils/mks5lboot/dualboot/dualboot.c b/utils/mks5lboot/dualboot/dualboot.c index d58c2299e2..64b013cf16 100644 --- a/utils/mks5lboot/dualboot/dualboot.c +++ b/utils/mks5lboot/dualboot/dualboot.c @@ -105,7 +105,27 @@ static uint16_t fatal[] = { 3000,500,500, 3000,500,500, 3000,500,0, 0 }; #define sad2 (&fatal[3]) #define sad (&fatal[6]) -/* iPod Classic: decrypted hashes for known OFs */ +/* Decrypted hashes for known OFs. + * + * identify_fw() decrypts the IM3 header's data_sign with the hardware UKEY + * and looks the result up here. Anything not listed is assumed to be a RB + * bootloader (FW_RB), so a model whose OF is missing from this table cannot + * be installed to or uninstalled from - both paths bail out before writing, + * which is safe but does nothing. The table is therefore per target. */ +#ifdef IPOD_NANO3G +/* iPod Nano 3G. The decrypted data_sign is the first SIGN_SZ bytes of the + * SHA-1 of the plaintext bootloader. Each firmware's updater (aupd) carries + * that bootloader, 0x1f800 bytes, in a NOR image; these were computed from + * the aupd of every release. v1.1.3 was also read from a 4GB unit's NOR. */ +static unsigned char of_sha[][SIGN_SZ] = { + "\xC1\x3E\xBC\x68\x7C\xCD\xE8\x22\xAD\xBD\x88\x94\xA9\x95\x21\x4B", /* v1.0.1 */ + "\xC4\x57\xFD\x04\x8E\xFB\xF1\x1A\x9D\xFA\x80\x11\x1E\x3F\xA6\x3E", /* v1.0.2 */ + "\x2E\x7F\xA3\x4A\xF1\xAA\x75\x4B\x34\xF7\xB9\xC4\x58\xCF\xBD\xDB", /* v1.0.3 */ + "\x15\xA6\x6A\xBF\xA4\x3D\xFA\x76\x9F\xCE\xCE\x39\x1F\x28\xEB\x47", /* v1.1 */ + "\x60\xAC\x5A\x12\x38\x65\x0D\x2B\xC6\x63\x15\x02\xA0\x44\x84\x39" /* v1.1.2 and v1.1.3 */ +}; +#else +/* iPod Classic */ static unsigned char of_sha[][SIGN_SZ] = { "\x31\xD1\x7A\x3A\x4B\x05\xE4\x09\x6B\x58\x31\x0A\xB7\x6C\x2F\x88", /* v1.0.1 */ "\x7C\xEF\x11\xD1\x3A\x96\xBA\x1C\x1C\x73\x42\x91\xF3\xFC\xBF\x20", /* v1.0.3 */ @@ -117,6 +137,7 @@ static unsigned char of_sha[][SIGN_SZ] = { "\x06\x85\xDF\x28\xE4\xD7\xF4\x82\xC0\x73\xB0\x53\x26\xFC\xB0\xFE", /* v2.0.4 */ "\x60\x80\x7D\x33\xA8\xDE\xF8\x49\xBB\xBE\x01\x45\xFF\x62\x40\x19" /* v2.0.5 */ }; +#endif #define N_OF (int)(sizeof(of_sha)/SIGN_SZ) /* we can assume that unknown FW is a RB bootloader */ diff --git a/utils/mks5lboot/main.c b/utils/mks5lboot/main.c index 206dcf4838..ffdd395256 100644 --- a/utils/mks5lboot/main.c +++ b/utils/mks5lboot/main.c @@ -138,7 +138,8 @@ static void usage(void) " is the name of the platform (type of device) for\n" " which the DFU uninstaller will be built. Currently supported\n" " platform names are:\n" - " ipod6g: iPod Classic 6G\n" + " ipod6g: iPod Classic 6G\n" + " ipodnano3g: iPod Nano 3G\n" "\n" "Options:\n" " -p, --pid Use a specific (Product Id) USB device,\n" diff --git a/utils/mks5lboot/mkdfu.c b/utils/mks5lboot/mkdfu.c index ff9ca7041c..63336ea520 100644 --- a/utils/mks5lboot/mkdfu.c +++ b/utils/mks5lboot/mkdfu.c @@ -55,6 +55,10 @@ const struct ipod_models ipod_identity[] = "Classic 6G", "ipod6g", "ip6g", 71, dualboot_install_ipod6g, sizeof(dualboot_install_ipod6g), dualboot_uninstall_ipod6g, sizeof(dualboot_uninstall_ipod6g) }, + [MODEL_IPODNANO3G] = { + "Nano 3G", "ipodnano3g", "nn3g", 117, + dualboot_install_ipodnano3g, sizeof(dualboot_install_ipodnano3g), + dualboot_uninstall_ipodnano3g, sizeof(dualboot_uninstall_ipodnano3g) }, }; struct Im3Info s5l8702hdr = diff --git a/utils/mks5lboot/mks5lboot.h b/utils/mks5lboot/mks5lboot.h index d0185eefa9..0f41f50ecd 100644 --- a/utils/mks5lboot/mks5lboot.h +++ b/utils/mks5lboot/mks5lboot.h @@ -78,6 +78,7 @@ struct Im3Hdr enum { MODEL_UNKNOWN = -1, MODEL_IPOD6G = 0, + MODEL_IPODNANO3G, /* new models go here */ NUM_MODELS diff --git a/utils/rbutilqt/CMakeLists.txt b/utils/rbutilqt/CMakeLists.txt index b3b564510f..2a4fcfcfd9 100644 --- a/utils/rbutilqt/CMakeLists.txt +++ b/utils/rbutilqt/CMakeLists.txt @@ -246,6 +246,18 @@ if(APPLE) target_sources(rbbase PRIVATE ${CMAKE_CURRENT_LIST_DIR}/base/ttscarbon.cpp ${CMAKE_CURRENT_LIST_DIR}/base/ttscarbon.h) + + # The below is only needed for ttscarbon + # So we can delete this whole if(APPLE) block at once + target_compile_options(rbbase PRIVATE + $<$:-Wno-deprecated-declarations> + ) +endif() + +if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + target_compile_options(RockboxUtility PRIVATE + $<$:-Wno-c++20-extensions> + ) endif() if (TARGET Qt${QT_VERSION_MAJOR}::TextToSpeech AND TARGET Qt${QT_VERSION_MAJOR}::Multimedia) diff --git a/utils/rbutilqt/RockboxUtility.desktop b/utils/rbutilqt/RockboxUtility.desktop index cd0fc08cfc..0ffe1b28bb 100644 --- a/utils/rbutilqt/RockboxUtility.desktop +++ b/utils/rbutilqt/RockboxUtility.desktop @@ -1,6 +1,5 @@ [Desktop Entry] GenericName=Rockbox Installer and Maintainance Tool -Version=1.5.2 Hidden=false Name=Rockbox Utility Type=Application diff --git a/utils/rbutilqt/base/autodetection.cpp b/utils/rbutilqt/base/autodetection.cpp index 820805ac69..d49fe03828 100644 --- a/utils/rbutilqt/base/autodetection.cpp +++ b/utils/rbutilqt/base/autodetection.cpp @@ -259,7 +259,7 @@ void Autodetection::mergePatcher(void) n = ipod_scan(&ipod); // FIXME: handle more than one Ipod connected in ipodpatcher. if(n == 1) { - LOG_INFO() << "Ipod found:" << ipod.modelstr << "at" << ipod.diskname; + LOG_INFO() << "iPod found:" << ipod.modelstr << "at" << ipod.diskname; // since resolveMountPoint is doing exact matches we need to select // the correct partition. QString mp(ipod.diskname); @@ -280,7 +280,7 @@ void Autodetection::mergePatcher(void) updateDetectedDevice(d); } else { - LOG_INFO() << "ipodpatcher: no Ipod found." << n; + LOG_INFO() << "ipodpatcher: no iPod found." << n; } ipod_dealloc_buffer(&ipod); diff --git a/utils/rbutilqt/base/bootloaderinstallipod.cpp b/utils/rbutilqt/base/bootloaderinstallipod.cpp index 1e5b1c39b2..fae93da994 100644 --- a/utils/rbutilqt/base/bootloaderinstallipod.cpp +++ b/utils/rbutilqt/base/bootloaderinstallipod.cpp @@ -83,7 +83,7 @@ void BootloaderInstallIpod::installStage2(void) QCoreApplication::processEvents(); if(ipod_reopen_rw(&ipod) < 0) { - emit logItem(tr("Could not open Ipod in R/W mode"), LOGERROR); + emit logItem(tr("Could not open iPod in R/W mode"), LOGERROR); emit done(true); return; } @@ -152,7 +152,7 @@ bool BootloaderInstallIpod::uninstall(void) } if (ipod_reopen_rw(&ipod) < 0) { - emit logItem(tr("Could not open Ipod in R/W mode"), LOGERROR); + emit logItem(tr("Could not open iPod in R/W mode"), LOGERROR); emit done(true); return false; } @@ -259,16 +259,16 @@ bool BootloaderInstallIpod::ipodInitialize(struct ipod_t *ipod) } int result = ipod_open(ipod, 1); if(result == -2) { - emit logItem(tr("Could not open Ipod: permission denied"), LOGERROR); + emit logItem(tr("Could not open iPod: permission denied"), LOGERROR); return false; } else if(result < 0) { - emit logItem(tr("Could not open Ipod"), LOGERROR); + emit logItem(tr("Could not open iPod"), LOGERROR); return false; } if(read_partinfo(ipod, 1) < 0) { - emit logItem(tr("Error reading partition table - possibly not an Ipod"), LOGERROR); + emit logItem(tr("Error reading partition table - possibly not an iPod"), LOGERROR); ipod_close(ipod); return false; } diff --git a/utils/rbutilqt/base/bootloaderinstallsansa.cpp b/utils/rbutilqt/base/bootloaderinstallsansa.cpp index d2c17113e8..dbbab0f5ae 100644 --- a/utils/rbutilqt/base/bootloaderinstallsansa.cpp +++ b/utils/rbutilqt/base/bootloaderinstallsansa.cpp @@ -218,7 +218,7 @@ BootloaderInstallBase::BootloaderType BootloaderInstallSansa::installed(void) bool BootloaderInstallSansa::sansaInitialize(struct sansa_t *sansa) { - // if the ipod was already opened make sure to close it first. + // if the player was already opened make sure to close it first. #if defined(Q_OS_WIN32) if(sansa->dh != INVALID_HANDLE_VALUE) #else diff --git a/utils/rbutilqt/base/rbsettings.cpp b/utils/rbutilqt/base/rbsettings.cpp index 4c4790c513..6cb1353831 100644 --- a/utils/rbutilqt/base/rbsettings.cpp +++ b/utils/rbutilqt/base/rbsettings.cpp @@ -74,10 +74,10 @@ const static struct { { RbSettings::EncoderPath, ":encoder:/encoderpath", "" }, { RbSettings::EncoderOptions, ":encoder:/encoderoptions", "" }, { RbSettings::CacheDisabled, "cachedisable", "false" }, - { RbSettings::TtsUseSapi4, "sapi/useSapi4", "false" }, { RbSettings::EncoderNarrowBand, ":encoder:/narrowband", "false" }, { RbSettings::WavtrimThreshold, "wavtrimthreshold", "500"}, { RbSettings::TtsSpeed, ":tts:/speed", "175" }, + { RbSettings::TtsVolume, ":tts:/volume", "100" }, { RbSettings::EncoderComplexity, ":encoder:/complexity", "10" }, { RbSettings::EncoderQuality, ":encoder:/quality", "-1.0" }, { RbSettings::EncoderVolume, ":encoder:/volume", "1.0" }, diff --git a/utils/rbutilqt/base/rbsettings.h b/utils/rbutilqt/base/rbsettings.h index 55a4d8e4cf..cd525d5d5e 100644 --- a/utils/rbutilqt/base/rbsettings.h +++ b/utils/rbutilqt/base/rbsettings.h @@ -69,8 +69,8 @@ class RbSettings : public QObject WavtrimThreshold, EncoderComplexity, TtsSpeed, + TtsVolume, CacheDisabled, - TtsUseSapi4, EncoderNarrowBand, EncoderQuality, EncoderVolume, diff --git a/utils/rbutilqt/base/talkfile.cpp b/utils/rbutilqt/base/talkfile.cpp index 50afb1f323..40ceb03158 100644 --- a/utils/rbutilqt/base/talkfile.cpp +++ b/utils/rbutilqt/base/talkfile.cpp @@ -31,6 +31,34 @@ TalkFileCreator::TalkFileCreator(QObject* parent): QObject(parent) bool TalkFileCreator::createTalkFiles() { m_abort = false; + QStringList dirs = m_dirs; + if(dirs.isEmpty()) + dirs.append(m_dir); + + for(const QString& dir : dirs) { + if(m_abort) + break; + + m_dir = dir; + LOG_INFO() << "creating talk files for folder" << m_dir; + if(!createTalkFilesForDir()) { + emit done(true); + return false; + } + } + + if(m_abort) { + doAbort(); + emit done(true); + return false; + } + + emit done(false); + return true; +} + +bool TalkFileCreator::createTalkFilesForDir() +{ QString errStr; emit logItem(tr("Starting Talk file generation for folder %1") @@ -51,10 +79,10 @@ bool TalkFileCreator::createTalkFiles() // generate entries TalkGenerator generator(this); // no string corrections yet: do not set language for TalkGenerator. - connect(&generator, &TalkGenerator::done, this, &TalkFileCreator::done); connect(&generator, &TalkGenerator::logItem, this, &TalkFileCreator::logItem); connect(&generator, &TalkGenerator::logProgress, this, &TalkFileCreator::logProgress); - connect(this, &TalkFileCreator::aborted, &generator, &TalkGenerator::abort); + connect(this, &TalkFileCreator::aborted, + &generator, &TalkGenerator::abort, Qt::DirectConnection); if(generator.process(&m_talkList) == TalkGenerator::eERROR) { @@ -77,7 +105,6 @@ bool TalkFileCreator::createTalkFiles() emit logItem(tr("Finished creating Talk files"),LOGOK); emit logProgress(1,1); - emit done(false); return true; } @@ -101,7 +128,6 @@ void TalkFileCreator::doAbort() { cleanup(); emit logProgress(0,1); - emit done(true); } //! \brief creates a list of what to generate //! @@ -111,6 +137,20 @@ bool TalkFileCreator::createTalkList(QDir startDir) LOG_INFO() << "generating list of files" << startDir; m_talkList.clear(); + const QString startPath = QDir::cleanPath(startDir.absolutePath()); + auto isInIgnoredDirectory = [&startPath](const QFileInfo& fileInfo) { + QDir dir(fileInfo.isDir() ? fileInfo.absoluteFilePath() + : fileInfo.absolutePath()); + while(true) { + if(QFileInfo::exists(dir.filePath("talkclips.ignore"))) + return true; + + if(QDir::cleanPath(dir.absolutePath()) == startPath || !dir.cdUp()) + break; + } + return false; + }; + // create Iterator QDirIterator::IteratorFlags flags = QDirIterator::NoIteratorFlags; if(m_recursive) @@ -134,6 +174,10 @@ bool TalkFileCreator::createTalkList(QDir startDir) QFileInfo fileInf = it.fileInfo(); + // A talkclips.ignore file excludes its directory and all descendants. + if(isInIgnoredDirectory(fileInf)) + continue; + // its a dir if(fileInf.isDir()) { @@ -142,12 +186,6 @@ bool TalkFileCreator::createTalkList(QDir startDir) // insert into List if(!dir.dirName().isEmpty() && m_talkFolders) { - // check if we should ignore it - if(QFileInfo::exists(dir.path() + "/talkclips.ignore")) - { - continue; - } - // check to see if it's already covered if(m_generateOnlyNew && QFileInfo::exists(dir.path() + "/_dirname.talk")) { @@ -183,8 +221,8 @@ bool TalkFileCreator::createTalkList(QDir startDir) bool match = false; for(int i=0; i < m_ignoreFiles.size();i++) { - QString pattern = m_ignoreFiles[i].trimmed() - .replace("?", ".").replace("*", ".*"); + QString pattern = QRegularExpression::wildcardToRegularExpression( + m_ignoreFiles[i].trimmed()); QRegularExpression rx(pattern); if(rx.match(fileInf.fileName()).hasMatch()) match = true; diff --git a/utils/rbutilqt/base/talkfile.h b/utils/rbutilqt/base/talkfile.h index 014753da2a..3b57964aaf 100644 --- a/utils/rbutilqt/base/talkfile.h +++ b/utils/rbutilqt/base/talkfile.h @@ -22,6 +22,7 @@ #ifndef TALKFILE_H #define TALKFILE_H +#include #include #include "progressloglevels.h" @@ -37,6 +38,7 @@ public: bool createTalkFiles(); void setDir(QString dir) {m_dir = dir;} + void setDirs(QStringList dirs) {m_dirs = dirs;} void setMountPoint(QString mountpoint) {m_mountpoint = mountpoint;} void setGenerateOnlyNew(bool ov) {m_generateOnlyNew = ov;} @@ -55,6 +57,7 @@ signals: void logProgress(int, int); //! set progress bar. private: + bool createTalkFilesForDir(); bool cleanup(); QString stripExtension(QString filename); void doAbort(); @@ -64,6 +67,7 @@ private: bool createTalkList(QDir startDir); QString m_dir; + QStringList m_dirs; QString m_mountpoint; bool m_generateOnlyNew; @@ -73,7 +77,7 @@ private: bool m_talkFiles; QStringList m_ignoreFiles; - bool m_abort; + std::atomic_bool m_abort{false}; QList m_talkList; }; diff --git a/utils/rbutilqt/base/talkgenerator.h b/utils/rbutilqt/base/talkgenerator.h index 6bdb22745e..95da967d4d 100644 --- a/utils/rbutilqt/base/talkgenerator.h +++ b/utils/rbutilqt/base/talkgenerator.h @@ -22,6 +22,7 @@ #ifndef TALKGENERATOR_H #define TALKGENERATOR_H +#include #include #include "progressloglevels.h" @@ -81,7 +82,7 @@ private: }; QList m_corrections; - bool m_abort; + std::atomic_bool m_abort{false}; }; diff --git a/utils/rbutilqt/base/ttssapi.cpp b/utils/rbutilqt/base/ttssapi.cpp index 14c0cae70e..0a612213fa 100644 --- a/utils/rbutilqt/base/ttssapi.cpp +++ b/utils/rbutilqt/base/ttssapi.cpp @@ -29,6 +29,7 @@ TTSSapi::TTSSapi(QObject* parent) : TTSBase(parent) m_TTSTemplate << "/language:%lang"; m_TTSTemplate << "/voice:%voice"; m_TTSTemplate << "/speed:%speed"; + m_TTSTemplate << "/volume:%volume"; m_TTSTemplate << "%options"; m_TTSVoiceTemplate << "//nologo"; @@ -38,6 +39,8 @@ TTSSapi::TTSSapi(QObject* parent) : TTSBase(parent) m_TTSType = "sapi"; defaultLanguage = "english"; + voicescript = nullptr; + voicestream = nullptr; m_started = false; } @@ -72,6 +75,12 @@ void TTSSapi::generateSettings() speed = 0; insertSetting(eSPEED, new EncTtsSetting(this, EncTtsSetting::eINT, tr("Speed:"), speed, -10, 10)); + // volume + int volume = RbSettings::subValue(m_TTSType, RbSettings::TtsVolume).toInt(); + if(volume > 100 || volume < 0) + volume = 100; + insertSetting(eVOLUME, new EncTtsSetting(this, + EncTtsSetting::eINT, tr("Volume:"), volume, 0, 100)); // options insertSetting(eOPTIONS, new EncTtsSetting(this, EncTtsSetting::eSTRING, tr("Options:"), @@ -88,6 +97,8 @@ void TTSSapi::saveSettings() getSetting(eVOICE)->current().toString()); RbSettings::setSubValue(m_TTSType, RbSettings::TtsSpeed, getSetting(eSPEED)->current().toInt()); + RbSettings::setSubValue(m_TTSType, RbSettings::TtsVolume, + getSetting(eVOLUME)->current().toInt()); RbSettings::setSubValue(m_TTSType, RbSettings::TtsOptions, getSetting(eOPTIONS)->current().toString()); @@ -110,6 +121,7 @@ bool TTSSapi::start(QString *errStr) m_TTSLanguage =RbSettings::subValue(m_TTSType, RbSettings::TtsLanguage).toString(); m_TTSVoice=RbSettings::subValue(m_TTSType, RbSettings::TtsVoice).toString(); m_TTSSpeed=RbSettings::subValue(m_TTSType, RbSettings::TtsSpeed).toString(); + m_TTSVolume=RbSettings::subValue(m_TTSType, RbSettings::TtsVolume).toString(); QFile::remove(QDir::tempPath() +"/sapi_voice.vbs"); QFile::copy(":/builtin/sapi_voice.vbs",QDir::tempPath() + "/sapi_voice.vbs"); @@ -128,6 +140,7 @@ bool TTSSapi::start(QString *errStr) exec.replaceInStrings("%lang",m_TTSLanguage); exec.replaceInStrings("%voice",m_TTSVoice); exec.replaceInStrings("%speed",m_TTSSpeed); + exec.replaceInStrings("%volume",m_TTSVolume); LOG_INFO() << "Start: cscript " << exec; voicescript = new QProcess(nullptr); @@ -158,15 +171,24 @@ bool TTSSapi::start(QString *errStr) QString TTSSapi::voiceVendor(void) { bool keeprunning = m_started; - QString vendor; + QString vendor = "(unknown)"; if(!m_started) { QString error; - start(&error); + if(!start(&error)) { + LOG_ERROR() << "could not start SAPI while querying vendor:" << error; + return vendor; + } } *voicestream << "QUERY\tVENDOR\r\n"; voicestream->flush(); - while((vendor = voicestream->readLine()).isEmpty()) - QCoreApplication::processEvents(); + if(voicescript->waitForReadyRead(5000)) { + QString response = voicestream->readLine(); + if(!response.isEmpty()) + vendor = response; + } + else { + LOG_ERROR() << "SAPI timed out while querying the voice vendor"; + } LOG_INFO() << "TTS vendor:" << vendor; if(!keeprunning) { @@ -231,31 +253,93 @@ QStringList TTSSapi::getVoiceList(QString language) TTSStatus TTSSapi::voice(const QString& text, const QString& wavfile, QString *errStr) { - (void) errStr; QString query = "SPEAK\t"+wavfile+"\t"+text; LOG_INFO() << "voicing" << query; // append newline to query. Done now to keep debug output more readable. query.append("\r\n"); - *voicestream << query; - *voicestream << "SYNC\tbla\r\n"; - voicestream->flush(); - // do NOT poll the output with readLine(), this causes sync issues! - voicescript->waitForReadyRead(); - if(!QFileInfo(wavfile).isFile()) { - LOG_ERROR() << "output file does not exist:" << wavfile; - return FatalError; + // Some third-party SAPI voices occasionally stop responding after many + // consecutive requests. Restart the private cscript process and retry the + // current string instead of discarding the entire voice-file operation. + constexpr int maxAttempts = 3; + constexpr int responseTimeout = 20000; + for(int attempt = 1; attempt <= maxAttempts; ++attempt) { + QFile::remove(wavfile); + *voicestream << query; + *voicestream << "SYNC\tbla\r\n"; + voicestream->flush(); + + // Wait for the explicit SYNC reply. QProcess::waitForReadyRead() alone + // is insufficient: the script can also write warnings, and older code + // mistook those for completion before the wave file existed. + QElapsedTimer timer; + timer.start(); + bool synced = false; + while(timer.elapsed() < responseTimeout) { + int remaining = responseTimeout - static_cast(timer.elapsed()); + if(!voicescript->waitForReadyRead(remaining)) + break; + + QString response = voicestream->readLine(); + if(response == "bla") { + synced = true; + break; + } + if(response.startsWith("ERROR\t")) { + *errStr = response.mid(6); + LOG_ERROR() << "SAPI error:" << *errStr; + return FatalError; + } + } + + if(synced && QFileInfo(wavfile).isFile()) + return NoError; + + if(synced) { + *errStr = tr("SAPI did not create the output wave file"); + LOG_ERROR() << "output file does not exist:" << wavfile; + return FatalError; + } + + LOG_WARNING() << "SAPI timed out on attempt" << attempt + << "of" << maxAttempts << "for" << text; + if(attempt < maxAttempts) { + stop(); + QString startError; + if(!start(&startError)) { + *errStr = tr("Could not restart SAPI after a timeout: %1") + .arg(startError); + LOG_ERROR() << *errStr; + return FatalError; + } + } } - return NoError; + + *errStr = tr("SAPI timed out repeatedly while generating speech"); + LOG_ERROR() << *errStr; + return FatalError; } bool TTSSapi::stop() { + if(!m_started || voicescript == nullptr) + return true; + *voicestream << "QUIT\r\n"; voicestream->flush(); - voicescript->waitForFinished(); + if(!voicescript->waitForFinished(5000)) { + LOG_WARNING() << "SAPI process did not quit, terminating it"; + voicescript->terminate(); + if(!voicescript->waitForFinished(2000)) { + LOG_WARNING() << "SAPI process did not terminate, killing it"; + voicescript->kill(); + voicescript->waitForFinished(2000); + } + } delete voicestream; delete voicescript; + voicestream = nullptr; + voicescript = nullptr; QFile::setPermissions(QDir::tempPath() +"/sapi_voice.vbs", QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner | QFile::ReadUser | QFile::WriteUser | QFile::ExeUser diff --git a/utils/rbutilqt/base/ttssapi.h b/utils/rbutilqt/base/ttssapi.h index 8ea1370133..4aa8f25c4c 100644 --- a/utils/rbutilqt/base/ttssapi.h +++ b/utils/rbutilqt/base/ttssapi.h @@ -31,6 +31,7 @@ class TTSSapi : public TTSBase eLANGUAGE, eVOICE, eSPEED, + eVOLUME, eOPTIONS }; @@ -64,6 +65,7 @@ class TTSSapi : public TTSBase QString m_TTSLanguage; QString m_TTSVoice; QString m_TTSSpeed; + QString m_TTSVolume; bool m_started; protected: diff --git a/utils/rbutilqt/base/voicefile.cpp b/utils/rbutilqt/base/voicefile.cpp index 1daaa0b9a8..32845edcec 100644 --- a/utils/rbutilqt/base/voicefile.cpp +++ b/utils/rbutilqt/base/voicefile.cpp @@ -28,18 +28,21 @@ VoiceFileCreator::VoiceFileCreator(QObject* parent) :QObject(parent) { m_wavtrimThreshold=500; + corrFile = nullptr; } void VoiceFileCreator::abort() { - m_abort = true; emit aborted(); } bool VoiceFileCreator::createVoiceFile() { m_talkList.clear(); - m_abort = false; + delete corrFile; + corrFile = new QFile(":/builtin/voice-corrections.txt", this); + corrFile->open(QIODevice::ReadOnly); + emit logItem(tr("Starting Voicefile generation"),LOGINFO); // test if tempdir exists @@ -88,19 +91,17 @@ bool VoiceFileCreator::createVoiceFile() } if(cindex != -1) { LOG_INFO() << "extracting voice corrections file"; - QTemporaryFile corrfileT; - corrfileT.open(); - QString cfn = corrfileT.fileName(); + QTemporaryFile *corrfileT = new QTemporaryFile(this); + corrfileT->open(); + QString cfn = corrfileT->fileName(); if(z.extractArchive(cfn, QFileInfo(contents.at(cindex)).fileName())) { emit logItem(tr("Extracted voice corrections file from installation"), LOGINFO); - corrFile = &corrfileT; + delete corrFile; + corrFile = corrfileT; } else { // XXX try to fetch an updated file via voicecorrections_url ? - corrfileT.close(); + delete corrfileT; emit logItem(tr("Using internal voice corrections file"), LOGINFO); - QFile corrfile(":/builtin/voice-corrections.txt"); - corrfile.open(QIODevice::ReadOnly); - corrFile = &corrfile; } } if(eindex != -1) { @@ -182,7 +183,7 @@ bool VoiceFileCreator::createVoiceFile() // everything successful, now create the actual voice file. create(); - if (corrFile->isOpen()) + if (corrFile && corrFile->isOpen()) corrFile->close(); return true; } @@ -326,12 +327,13 @@ void VoiceFileCreator::create(void) { TalkGenerator generator(this); // set language for string correction. If not set no correction will be made. - if(useCorrection) + if(useCorrection && corrFile) generator.setLang(m_lang, corrFile); connect(&generator, &TalkGenerator::done, this, &VoiceFileCreator::done); connect(&generator, &TalkGenerator::logItem, this, &VoiceFileCreator::logItem); connect(&generator, &TalkGenerator::logProgress, this, &VoiceFileCreator::logProgress); - connect(this, &VoiceFileCreator::aborted, &generator, &TalkGenerator::abort); + connect(this, &VoiceFileCreator::aborted, + &generator, &TalkGenerator::abort, Qt::DirectConnection); if(generator.process(&m_talkList, m_wavtrimThreshold) == TalkGenerator::eERROR) { diff --git a/utils/rbutilqt/base/voicefile.h b/utils/rbutilqt/base/voicefile.h index 2c63ae4c80..35abe2ec60 100644 --- a/utils/rbutilqt/base/voicefile.h +++ b/utils/rbutilqt/base/voicefile.h @@ -70,7 +70,6 @@ private: int m_voiceformat; QFile *corrFile; // the voice-corrections file - bool m_abort; QList m_talkList; }; diff --git a/utils/rbutilqt/base/zipinstaller.cpp b/utils/rbutilqt/base/zipinstaller.cpp index f97adfb492..77caf7b937 100644 --- a/utils/rbutilqt/base/zipinstaller.cpp +++ b/utils/rbutilqt/base/zipinstaller.cpp @@ -23,13 +23,118 @@ #include "ziputil.h" #include "Logger.h" +class ZipInstallThread : public QThread +{ +public: + enum Result { + Success, + OpenFailed, + NotEnoughSpace, + ExtractionFailed, + CopyFailed + }; + + explicit ZipInstallThread(ZipInstaller *owner) + : QThread(owner), m_owner(owner), m_unzip(true), + m_result(OpenFailed), m_requiredSpace(0) + { + } + + void setArchive(const QString& file, const QString& mountpoint) + { + m_file = file; + m_mountpoint = mountpoint; + m_unzip = true; + } + + void setCopy(const QString& file, const QString& mountpoint, + const QString& target) + { + m_file = file; + m_mountpoint = mountpoint; + m_target = target; + m_unzip = false; + } + + Result result(void) const { return m_result; } + QStringList installedFiles(void) const { return m_installedFiles; } +protected: + void run(void) override + { + if(m_unzip) { + ZipUtil zip(nullptr); + connect(&zip, &ZipUtil::logProgress, + m_owner, &ZipInstaller::logProgress); + connect(&zip, &ZipUtil::logItem, + m_owner, &ZipInstaller::logItem); + + if(!zip.open(m_file, QuaZip::mdUnzip)) { + m_result = OpenFailed; + return; + } + + m_requiredSpace = zip.totalUncompressedSize( + Utils::filesystemClusterSize(m_mountpoint)); + if((qint64)Utils::filesystemFree(m_mountpoint) + < m_requiredSpace + 1000000) { + zip.close(); + m_result = NotEnoughSpace; + return; + } + + m_installedFiles = zip.files(); + if(!zip.extractArchive(m_mountpoint)) { + m_result = ExtractionFailed; + zip.close(); + return; + } + zip.close(); + } + else { + QString destfile = m_mountpoint + "/" + m_target; + QString path = QFileInfo(destfile).absolutePath(); + if(!QDir().mkpath(path)) { + m_result = CopyFailed; + return; + } + QFile(destfile).remove(); + if(!QFile::copy(m_file, destfile)) { + m_result = CopyFailed; + return; + } + m_installedFiles.append(m_target); + } + + m_result = Success; + } + +private: + ZipInstaller *m_owner; + QString m_file; + QString m_mountpoint; + QString m_target; + QStringList m_installedFiles; + bool m_unzip; + Result m_result; + qint64 m_requiredSpace; +}; + ZipInstaller::ZipInstaller(QObject* parent) : QObject(parent), - m_unzip(true), m_usecache(false), m_getter(nullptr) + m_unzip(true), m_usecache(false), m_getter(nullptr), + m_installThread(nullptr) { } +ZipInstaller::~ZipInstaller() +{ + if(m_installThread && m_installThread->isRunning()) { + m_installThread->wait(); + } +} + + void ZipInstaller::install() { LOG_INFO() << "initializing installation"; @@ -106,7 +211,6 @@ void ZipInstaller::installStart() void ZipInstaller::downloadDone(QNetworkReply::NetworkError error) { LOG_INFO() << "download done, error:" << error; - QStringList zipContents; // needed later // update progress bar emit logProgress(1, 1); @@ -128,64 +232,57 @@ void ZipInstaller::downloadDone(QNetworkReply::NetworkError error) else { emit logItem(tr("Download finished."),LOGOK); } - QCoreApplication::processEvents(); if(m_unzip) { - // unzip downloaded file LOG_INFO() << "about to unzip" << m_file << "to" << m_mountpoint; - emit logItem(tr("Extracting file."), LOGINFO); - QCoreApplication::processEvents(); - - ZipUtil zip(this); - connect(&zip, &ZipUtil::logProgress, this, &ZipInstaller::logProgress); - connect(&zip, &ZipUtil::logItem, this, &ZipInstaller::logItem); - zip.open(m_file, QuaZip::mdUnzip); - // check for free space. Make sure after installation will still be - // some room for operating (also includes calculation mistakes due to - // cluster sizes on the player). - if((qint64)Utils::filesystemFree(m_mountpoint) - < (zip.totalUncompressedSize( - Utils::filesystemClusterSize(m_mountpoint)) - + 1000000)) { - emit logItem(tr("Not enough disk space! Aborting."), LOGERROR); - emit logProgress(1, 1); - emit done(true); - return; - } - zipContents = zip.files(); - if(!zip.extractArchive(m_mountpoint)) { - emit logItem(tr("Extraction failed!"), LOGERROR); - emit logProgress(1, 1); - emit done(true); - return; - } - zip.close(); + m_installThread = new ZipInstallThread(this); + m_installThread->setArchive(m_file, m_mountpoint); } else { if (m_target.isEmpty()) m_target = QUrl(m_url).fileName(); QString destfile = m_mountpoint + "/" + m_target; - // only copy the downloaded file to the output location / name emit logItem(tr("Installing file."), LOGINFO); LOG_INFO() << "saving downloaded file (no extraction) to" << destfile; + // Keep the temporary file materialized while the worker copies it. + m_downloadFile->open(); + m_installThread = new ZipInstallThread(this); + m_installThread->setCopy(m_file, m_mountpoint, m_target); + } - m_downloadFile->open(); // copy fails if file is not opened (filename issue?) - // make sure the required path is existing - QString path = QFileInfo(destfile).absolutePath(); - QDir p; - p.mkpath(path); - // QFile::copy() doesn't overwrite files, so remove old one first - // TODO: compare old and new file and fail if those are different. - QFile(destfile).remove(); - if(!m_downloadFile->copy(destfile)) { + connect(m_installThread, &QThread::finished, + this, &ZipInstaller::installFinished); + m_installThread->start(QThread::LowPriority); +} + + +void ZipInstaller::installFinished() +{ + ZipInstallThread *thread = m_installThread; + m_installThread = nullptr; + ZipInstallThread::Result result = thread->result(); + QStringList zipContents = thread->installedFiles(); + thread->deleteLater(); + + emit logProgress(1, 1); + switch(result) { + case ZipInstallThread::Success: + break; + case ZipInstallThread::NotEnoughSpace: + emit logItem(tr("Not enough disk space! Aborting."), LOGERROR); + emit done(true); + return; + case ZipInstallThread::CopyFailed: emit logItem(tr("Installing file failed."), LOGERROR); emit done(true); return; - } - - // add file to log - zipContents.append(m_target); + case ZipInstallThread::OpenFailed: + case ZipInstallThread::ExtractionFailed: + emit logItem(tr("Extraction failed!"), LOGERROR); + emit done(true); + return; } + if(m_logver.isEmpty()) { // if no version info is set use the timestamp of the server file. m_logver = m_getter->timestamp().toString(Qt::ISODate); diff --git a/utils/rbutilqt/base/zipinstaller.h b/utils/rbutilqt/base/zipinstaller.h index 87c38b7700..462ccfd988 100644 --- a/utils/rbutilqt/base/zipinstaller.h +++ b/utils/rbutilqt/base/zipinstaller.h @@ -28,6 +28,8 @@ #include "httpget.h" #include "Logger.h" +class ZipInstallThread; + /** Install a file or zip. * Downloads file(s) from a given URL, and installs by either extracting or * copying it to the target path set by setMountpoint(). @@ -37,6 +39,7 @@ class ZipInstaller : public QObject Q_OBJECT public: ZipInstaller(QObject* parent); + ~ZipInstaller(); void install(void); void setMountPoint(QString& mountpoint) {m_mountpoint = mountpoint;} void setUrl(QString& url){m_urllist = QStringList(url);} @@ -59,6 +62,7 @@ public slots: private slots: void downloadDone(QNetworkReply::NetworkError error); + void installFinished(void); void installStart(void); void installContinue(void); @@ -79,6 +83,7 @@ private: HttpGet *m_getter; QTemporaryFile *m_downloadFile; + ZipInstallThread *m_installThread; }; diff --git a/utils/rbutilqt/changelog.txt b/utils/rbutilqt/changelog.txt index d656142e69..d0ebc4ea21 100644 --- a/utils/rbutilqt/changelog.txt +++ b/utils/rbutilqt/changelog.txt @@ -43,7 +43,7 @@ Version 1.5.0 * Server Downloads changed to https. Version 1.5.1 -* Fix bootloader installation for Ipods. +* Fix bootloader installation for iPods. * Fix bootloader installation for Sandisk players. * Improve responsiveness on install / uninstall. * Enable Themes installation if themes are selected. diff --git a/utils/rbutilqt/configure.cpp b/utils/rbutilqt/configure.cpp index 7b6d634c3f..a3c2484c7b 100644 --- a/utils/rbutilqt/configure.cpp +++ b/utils/rbutilqt/configure.cpp @@ -797,7 +797,7 @@ void Config::autodetect() if(PlayerBuildInfo::instance()->value( PlayerBuildInfo::BootloaderMethod, detected.at(0).device) == "ipod") { msg = tr("%1 \"MacPod\" found!\n" - "Rockbox needs a FAT formatted Ipod (so-called \"WinPod\") " + "Rockbox needs a FAT formatted iPod (so-called \"WinPod\") " "to run. ").arg(PlayerBuildInfo::instance()->value( PlayerBuildInfo::DisplayName, detected.at(0).device).toString()); @@ -939,17 +939,20 @@ void Config::testTts() } QString filename; - QTemporaryFile file(this); + QTemporaryDir* tempDir = nullptr; // keep filename empty if the TTS can do speaking for itself. if(!(tts->capabilities() & TTSBase::CanSpeak)) { - file.open(); - filename = file.fileName(); - file.close(); + // SAPI's SpFileStream requires a filename with a wave extension. + // Give the engine a path that does not exist yet, matching normal + // voice-file generation, and keep its directory until playback ends. + tempDir = new QTemporaryDir(); + filename = tempDir->filePath("tts-test.wav"); } if(tts->voice(tr("Rockbox Utility Voice Test"),filename,&errstr) == FatalError) { tts->stop(); + delete tempDir; QMessageBox::warning(this,tr("Could not voice test string."), tr("Could not voice test string.\n") + errstr + tr("\nPlease configure TTS engine.")); @@ -957,17 +960,28 @@ void Config::testTts() return; } tts->stop(); - if(!filename.isEmpty()) { - QSoundEffect effect; - effect.setSource(QUrl::fromLocalFile(filename)); - effect.setLoopCount(0); - effect.setVolume(1.0f); - effect.play(); - } ui.testTTS->setEnabled(true); delete tts; /* Config objects are never deleted (in fact, they are leaked..), so we can't rely on QObject, since that would delete the TTSBase instance on application exit */ + if(tempDir != nullptr) { + QSoundEffect* effect = new QSoundEffect(this); + connect(effect, &QObject::destroyed, [tempDir]() { + delete tempDir; + }); + connect(effect, &QSoundEffect::playingChanged, effect, [effect]() { + if(!effect->isPlaying()) + effect->deleteLater(); + }); + connect(effect, &QSoundEffect::statusChanged, effect, [effect]() { + if(effect->status() == QSoundEffect::Error) + effect->deleteLater(); + }); + effect->setSource(QUrl::fromLocalFile(filename)); + effect->setLoopCount(1); + effect->setVolume(1.0f); + effect->play(); + } #endif } diff --git a/utils/rbutilqt/createvoicewindow.cpp b/utils/rbutilqt/createvoicewindow.cpp index f4679a2418..2d44ab0d32 100644 --- a/utils/rbutilqt/createvoicewindow.cpp +++ b/utils/rbutilqt/createvoicewindow.cpp @@ -28,7 +28,6 @@ CreateVoiceWindow::CreateVoiceWindow(QWidget *parent) : QDialog(parent) { ui.setupUi(this); - voicecreator = new VoiceFileCreator(this); updateSettings(); connect(ui.change,&QAbstractButton::clicked,this,&CreateVoiceWindow::change); } @@ -46,12 +45,22 @@ void CreateVoiceWindow::change() void CreateVoiceWindow::accept() { + // Do not start a second job while voice creation is still running. + if(workerThread) + return; + logger = new ProgressLoggerGui(this); connect(logger,&ProgressLoggerGui::closed,this,&QWidget::close); logger->show(); saveSettings(); + // Voice generation invokes the TTS engine and encoder synchronously. Run + // it in a worker thread so the GUI (including accessibility support) stays + // responsive while a voice file is being created. + workerThread = new QThread(this); + voicecreator = new VoiceFileCreator(nullptr); + //configure voicecreator QString suffix = RbSettings::value(RbSettings::Suffix).toString(); QString mountpoint = RbSettings::value(RbSettings::Mountpoint).toString(); @@ -70,8 +79,25 @@ void CreateVoiceWindow::accept() connect(voicecreator, &VoiceFileCreator::done, logger, &ProgressLoggerGui::setFinished); connect(voicecreator, &VoiceFileCreator::logItem, logger, &ProgressLoggerGui::addItem); connect(voicecreator, &VoiceFileCreator::logProgress, logger, &ProgressLoggerGui::setProgress); - connect(logger,&ProgressLoggerGui::aborted,voicecreator,&VoiceFileCreator::abort); - voicecreator->createVoiceFile(); + // abort() only emits a signal connected to the generator's atomic abort + // flag, so it is safe to call while the worker is inside a TTS request. + connect(logger, &ProgressLoggerGui::aborted, + voicecreator, &VoiceFileCreator::abort, Qt::DirectConnection); + + voicecreator->moveToThread(workerThread); + connect(workerThread, &QThread::started, + voicecreator, &VoiceFileCreator::createVoiceFile); + connect(voicecreator, &VoiceFileCreator::done, + workerThread, &QThread::quit); + connect(voicecreator, &VoiceFileCreator::done, + voicecreator, &QObject::deleteLater); + connect(workerThread, &QThread::finished, + workerThread, &QObject::deleteLater); + connect(workerThread, &QThread::finished, this, [this]() { + voicecreator = nullptr; + workerThread = nullptr; + }); + workerThread->start(QThread::LowPriority); } diff --git a/utils/rbutilqt/createvoicewindow.h b/utils/rbutilqt/createvoicewindow.h index 87127267c5..5e5c07caf6 100644 --- a/utils/rbutilqt/createvoicewindow.h +++ b/utils/rbutilqt/createvoicewindow.h @@ -22,6 +22,8 @@ #define CREATEVOICEWINDOW_H #include +#include +#include #include #include "ui_createvoicefrm.h" @@ -47,7 +49,8 @@ class CreateVoiceWindow : public QDialog private: void changeEvent(QEvent *event); - VoiceFileCreator* voicecreator; + QPointer voicecreator; + QPointer workerThread; Ui::CreateVoiceFrm ui; ProgressLoggerGui* logger; QUrl m_proxy; diff --git a/utils/rbutilqt/encttscfggui.cpp b/utils/rbutilqt/encttscfggui.cpp index 35e73ae907..f17021ebd1 100644 --- a/utils/rbutilqt/encttscfggui.cpp +++ b/utils/rbutilqt/encttscfggui.cpp @@ -219,10 +219,18 @@ QWidget* EncTtsCfgGui::createWidgets(EncTtsSetting* setting) checkbox->setAccessibleName(setting->name()); checkbox->setCheckState(setting->current().toBool() == true ? Qt::Checked : Qt::Unchecked); +#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0) connect(checkbox, &QCheckBox::checkStateChanged, this, [=, this](int value) { this->m_settingsWidgetsMap.key(checkbox)->setCurrent(value, false); }); +#else + connect(checkbox, &QCheckBox::stateChanged, + this, [=, this](int value) { + this->m_settingsWidgetsMap.key(checkbox)->setCurrent(value, false); + }); + +#endif value = checkbox; connect(setting, &EncTtsSetting::updateGui, this, [checkbox, setting]() { diff --git a/utils/rbutilqt/installtalkwindow.cpp b/utils/rbutilqt/installtalkwindow.cpp index c8bcdcec2e..be6c93b1fb 100644 --- a/utils/rbutilqt/installtalkwindow.cpp +++ b/utils/rbutilqt/installtalkwindow.cpp @@ -27,7 +27,6 @@ InstallTalkWindow::InstallTalkWindow(QWidget *parent) : QDialog(parent) { ui.setupUi(this); - talkcreator = new TalkFileCreator(this); connect(ui.change,&QAbstractButton::clicked,this,&InstallTalkWindow::change); @@ -47,6 +46,7 @@ InstallTalkWindow::InstallTalkWindow(QWidget *parent) : QDialog(parent) ui.treeView->setHeaderHidden(true); updateSettings(); + ui.treeView->setFocus(Qt::OtherFocusReason); } @@ -88,6 +88,9 @@ void InstallTalkWindow::change() void InstallTalkWindow::accept() { + if(workerThread) + return; + saveSettings(); QStringList foldersToTalk = RbSettings::value(RbSettings::TalkFolders).toStringList(); @@ -103,26 +106,41 @@ void InstallTalkWindow::accept() connect(logger,&ProgressLoggerGui::closed,this,&QWidget::close); logger->show(); + workerThread = new QThread(this); + talkcreator = new TalkFileCreator(nullptr); + talkcreator->setMountPoint(RbSettings::value(RbSettings::Mountpoint).toString()); + talkcreator->setDirs(foldersToTalk); talkcreator->setGenerateOnlyNew(ui.GenerateOnlyNew->isChecked()); talkcreator->setRecursive(ui.recursive->isChecked()); talkcreator->setStripExtensions(ui.StripExtensions->isChecked()); talkcreator->setTalkFolders(ui.talkFolders->isChecked()); talkcreator->setTalkFiles(ui.talkFiles->isChecked()); - talkcreator->setIgnoreFiles(ui.ignoreFiles->text().split(",", Qt::SkipEmptyParts)); + talkcreator->setIgnoreFiles(ui.ignoreEnabled->isChecked() + ? ui.ignoreFiles->text().split(",", Qt::SkipEmptyParts) + : QStringList()); connect(talkcreator, &TalkFileCreator::done, logger, &ProgressLoggerGui::setFinished); connect(talkcreator, &TalkFileCreator::logItem, logger, &ProgressLoggerGui::addItem); connect(talkcreator, &TalkFileCreator::logProgress, logger, &ProgressLoggerGui::setProgress); - connect(logger,&ProgressLoggerGui::aborted,talkcreator,&TalkFileCreator::abort); + connect(logger, &ProgressLoggerGui::aborted, + talkcreator, &TalkFileCreator::abort, Qt::DirectConnection); - for(int i = 0; i < foldersToTalk.size(); i++) { - LOG_INFO() << "creating talk files for folder" - << foldersToTalk.at(i); - talkcreator->setDir(foldersToTalk.at(i)); - talkcreator->createTalkFiles(); - } + talkcreator->moveToThread(workerThread); + connect(workerThread, &QThread::started, + talkcreator, &TalkFileCreator::createTalkFiles); + connect(talkcreator, &TalkFileCreator::done, + workerThread, &QThread::quit); + connect(talkcreator, &TalkFileCreator::done, + talkcreator, &QObject::deleteLater); + connect(workerThread, &QThread::finished, + workerThread, &QObject::deleteLater); + connect(workerThread, &QThread::finished, this, [this]() { + talkcreator = nullptr; + workerThread = nullptr; + }); + workerThread->start(QThread::LowPriority); } diff --git a/utils/rbutilqt/installtalkwindow.h b/utils/rbutilqt/installtalkwindow.h index f965e4cb09..3b2c34f04d 100644 --- a/utils/rbutilqt/installtalkwindow.h +++ b/utils/rbutilqt/installtalkwindow.h @@ -23,6 +23,8 @@ #include #include +#include +#include #include "ui_installtalkfrm.h" #include "progressloggergui.h" @@ -47,7 +49,8 @@ class InstallTalkWindow : public QDialog private: void changeEvent(QEvent *event); - TalkFileCreator* talkcreator; + QPointer talkcreator; + QPointer workerThread; Ui::InstallTalkFrm ui; ProgressLoggerGui* logger; QFileSystemModel *fsm; diff --git a/utils/rbutilqt/lang/rbutil_cs.ts b/utils/rbutilqt/lang/rbutil_cs.ts index bc75a3da39..2b95422a4f 100644 --- a/utils/rbutilqt/lang/rbutil_cs.ts +++ b/utils/rbutilqt/lang/rbutil_cs.ts @@ -669,8 +669,8 @@ Viz https://www.rockbox.org/wiki/IpodConversionToFAT32 - Could not open Ipod in R/W mode - Nelze otevřít Ipod pro čtení a zápis + Could not open iPod in R/W mode + Nelze otevřít iPod pro čtení a zápis @@ -724,18 +724,18 @@ Viz https://www.rockbox.org/wiki/IpodConversionToFAT32 - Could not open Ipod: permission denied - Nelze otevřít Ipod: nepovolený přístup + Could not open iPod: permission denied + Nelze otevřít iPod: nepovolený přístup - Could not open Ipod - Nelze otevřít Ipod + Could not open iPod + Nelze otevřít iPod - Error reading partition table - possibly not an Ipod - Chyba při čtení tabulky oddílů - možná to není Ipod + Error reading partition table - possibly not an iPod + Chyba při čtení tabulky oddílů - možná to není iPod @@ -1459,9 +1459,9 @@ Musíte přepnout váš přehrávač do MSC módu, aby instalace proběhla. %1 "MacPod" found! -Rockbox needs a FAT formatted Ipod (so-called "WinPod") to run. +Rockbox needs a FAT formatted iPod (so-called "WinPod") to run. Nalezen %1 "MacPod"! -Rockbox potřebuje k běhu Ipod naformátovaný jako FAT (tzv. "WinPod"). +Rockbox potřebuje k běhu iPod naformátovaný jako FAT (tzv. "WinPod"). Fatal error diff --git a/utils/rbutilqt/lang/rbutil_de.ts b/utils/rbutilqt/lang/rbutil_de.ts index 9c17153a8d..a85e1af7a3 100644 --- a/utils/rbutilqt/lang/rbutil_de.ts +++ b/utils/rbutilqt/lang/rbutil_de.ts @@ -664,8 +664,8 @@ Siehe https://www.rockbox.org/wiki/IpodConversionToFAT32 - Could not open Ipod in R/W mode - Kann Ipod nicht im R/W-Modus öffnen + Could not open iPod in R/W mode + Kann iPod nicht im R/W-Modus öffnen @@ -704,13 +704,13 @@ Siehe https://www.rockbox.org/wiki/IpodConversionToFAT32 - Could not open Ipod: permission denied - Konnte Ipod nicht öffnen: Zugriff verweigert + Could not open iPod: permission denied + Konnte iPod nicht öffnen: Zugriff verweigert - Could not open Ipod - Konnte Ipod nicht öffnen + Could not open iPod + Konnte iPod nicht öffnen @@ -739,8 +739,8 @@ Siehe https://www.rockbox.org/wiki/IpodConversionToFAT32 - Error reading partition table - possibly not an Ipod - Fehler beim Lesen der Partitionstabelle - möglicherweise kein Ipod + Error reading partition table - possibly not an iPod + Fehler beim Lesen der Partitionstabelle - möglicherweise kein iPod @@ -1425,9 +1425,9 @@ Rockbox Utility kann mit diesen Proxy-Einstellungen nicht arbeiten. Bitte sicher %1 "MacPod" found! -Rockbox needs a FAT formatted Ipod (so-called "WinPod") to run. +Rockbox needs a FAT formatted iPod (so-called "WinPod") to run. %1 "MacPod" gefunden! -Rockbox benötigt einen mit dem Dateisystem FAT formatierten Ipod (sogenannter "WinPod"). +Rockbox benötigt einen mit dem Dateisystem FAT formatierten iPod (sogenannter "WinPod"). diff --git a/utils/rbutilqt/lang/rbutil_fi.ts b/utils/rbutilqt/lang/rbutil_fi.ts index ea670b126a..33ee8af75e 100644 --- a/utils/rbutilqt/lang/rbutil_fi.ts +++ b/utils/rbutilqt/lang/rbutil_fi.ts @@ -655,7 +655,7 @@ See https://www.rockbox.org/wiki/IpodConversionToFAT32 - Could not open Ipod in R/W mode + Could not open iPod in R/W mode @@ -705,12 +705,12 @@ See https://www.rockbox.org/wiki/IpodConversionToFAT32 - Could not open Ipod: permission denied + Could not open iPod: permission denied - Could not open Ipod + Could not open iPod @@ -730,7 +730,7 @@ See https://www.rockbox.org/wiki/IpodConversionToFAT32 - Error reading partition table - possibly not an Ipod + Error reading partition table - possibly not an iPod @@ -1289,7 +1289,7 @@ Rockbox Utility can't work with this proxy settings. Make sure the system p %1 "MacPod" found! -Rockbox needs a FAT formatted Ipod (so-called "WinPod") to run. +Rockbox needs a FAT formatted iPod (so-called "WinPod") to run. diff --git a/utils/rbutilqt/lang/rbutil_fr.ts b/utils/rbutilqt/lang/rbutil_fr.ts index da7c181ef0..e5c3692f83 100644 --- a/utils/rbutilqt/lang/rbutil_fr.ts +++ b/utils/rbutilqt/lang/rbutil_fr.ts @@ -669,8 +669,8 @@ Veuillez vous référer à https://www.rockbox.org/wiki/IpodConversionToFAT32 - Could not open Ipod in R/W mode - Impossible d'accéder à l'Ipod en lecture et écriture + Could not open iPod in R/W mode + Impossible d'accéder à l'iPod en lecture et écriture @@ -719,13 +719,13 @@ Veuillez vous référer à https://www.rockbox.org/wiki/IpodConversionToFAT32 - Could not open Ipod: permission denied - Impossible d'accéder à l'Ipod: accès refusé + Could not open iPod: permission denied + Impossible d'accéder à l'iPod: accès refusé - Could not open Ipod - Impossible d'accéder à l'Ipod + Could not open iPod + Impossible d'accéder à l'iPod @@ -744,8 +744,8 @@ Veuillez vous référer à https://www.rockbox.org/wiki/IpodConversionToFAT32 - Error reading partition table - possibly not an Ipod - Erreur lors de la lecteur de la table des partitions; ce lecteur audio n'est peut-être pas un Ipod + Error reading partition table - possibly not an iPod + Erreur lors de la lecteur de la table des partitions; ce lecteur audio n'est peut-être pas un iPod @@ -1256,9 +1256,9 @@ L'Utilitaire Rockbox ne peut pas fonctionner avec ces réglages proxy. Vér %1 "MacPod" found! -Rockbox needs a FAT formatted Ipod (so-called "WinPod") to run. +Rockbox needs a FAT formatted iPod (so-called "WinPod") to run. %1 "MacPod" détecté ! -Rockbox ne peut fonctionner que sur un Ipod utilisant le système de fichier FAT (aussi appelé un "WinPod"). +Rockbox ne peut fonctionner que sur un iPod utilisant le système de fichier FAT (aussi appelé un "WinPod"). diff --git a/utils/rbutilqt/lang/rbutil_gr.ts b/utils/rbutilqt/lang/rbutil_gr.ts index a4d1e9ca70..185222f869 100644 --- a/utils/rbutilqt/lang/rbutil_gr.ts +++ b/utils/rbutilqt/lang/rbutil_gr.ts @@ -659,8 +659,8 @@ See https://www.rockbox.org/wiki/IpodConversionToFAT32 - Could not open Ipod in R/W mode - Αδύνατη η προσπέλαση του ipod με τρόπο RW + Could not open iPod in R/W mode + Αδύνατη η προσπέλαση του iPod με τρόπο RW @@ -709,13 +709,13 @@ See https://www.rockbox.org/wiki/IpodConversionToFAT32 - Could not open Ipod: permission denied + Could not open iPod: permission denied - Could not open Ipod - Αδύνατο το άνοιγμα του ipod + Could not open iPod + Αδύνατο το άνοιγμα του iPod @@ -734,7 +734,7 @@ See https://www.rockbox.org/wiki/IpodConversionToFAT32 - Error reading partition table - possibly not an Ipod + Error reading partition table - possibly not an iPod @@ -1310,7 +1310,7 @@ Rockbox Utility can't work with this proxy settings. Make sure the system p %1 "MacPod" found! -Rockbox needs a FAT formatted Ipod (so-called "WinPod") to run. +Rockbox needs a FAT formatted iPod (so-called "WinPod") to run. diff --git a/utils/rbutilqt/lang/rbutil_he.ts b/utils/rbutilqt/lang/rbutil_he.ts index 93f1c6861e..a71bbbf04f 100644 --- a/utils/rbutilqt/lang/rbutil_he.ts +++ b/utils/rbutilqt/lang/rbutil_he.ts @@ -660,7 +660,7 @@ See https://www.rockbox.org/wiki/IpodConversionToFAT32 - Could not open Ipod in R/W mode + Could not open iPod in R/W mode לא מצליח לפתוח את האייפוד במצב קריאה/כתיבה @@ -710,12 +710,12 @@ See https://www.rockbox.org/wiki/IpodConversionToFAT32 - Could not open Ipod: permission denied + Could not open iPod: permission denied לא מצליח לפתוח את האייפוד: הגישה נדחתה - Could not open Ipod + Could not open iPod לא מצליח לפתוח אייפוד @@ -735,7 +735,7 @@ See https://www.rockbox.org/wiki/IpodConversionToFAT32 - Error reading partition table - possibly not an Ipod + Error reading partition table - possibly not an iPod שגיאה בקריאת טבלת המחיצות - ככל הנראה נגן זה אינו אייפוד @@ -1313,7 +1313,7 @@ Rockbox Utility can't work with this proxy settings. Make sure the system p %1 "MacPod" found! -Rockbox needs a FAT formatted Ipod (so-called "WinPod") to run. +Rockbox needs a FAT formatted iPod (so-called "WinPod") to run. נמצא %1 "MacPod"! רוקבוקס זקוקה לאייפוד המפורמט בשיטת FAT (נגנים אלי מכונים WinPod) על מנת לרוץ. diff --git a/utils/rbutilqt/lang/rbutil_it.ts b/utils/rbutilqt/lang/rbutil_it.ts index 075037a0ab..6766057ff6 100644 --- a/utils/rbutilqt/lang/rbutil_it.ts +++ b/utils/rbutilqt/lang/rbutil_it.ts @@ -7,77 +7,77 @@ Backup - Backup + Backup <html><head/><body><p>This dialog will create a backup by archiving the contents of the Rockbox installation on the player into a zip file. This will include installed themes and settings stored below the .rockbox folder on the player.</p><p>The backup filename will be created based on the installed version. </p></body></html> - + <html><head/><body><p>Questa finestra crea una copia di sicurezza archiviando in un file ZIP il contenuto dell'installazione di Rockbox presente sul lettore. Saranno inclusi i temi installati e le impostazioni memorizzate nella cartella .rockbox del lettore.</p><p>Il nome del file di backup verrà generato in base alla versione installata.</p></body></html> Size: unknown - + Dimensione: sconosciuta Backup to: unknown - + Destinazione del backup: sconosciuta &Change - &Cambia + &Cambia &Backup - + &Backup &Cancel - + &Annulla Installation size: calculating ... - + Dimensione dell'installazione: calcolo in corso... Select Backup Filename - Scegliere un nome per il file di backup + Scegli il nome del file di backup Installation size: %L1 %2 - + Dimensione dell'installazione: %L1 %2 File exists - + Il file esiste già The selected backup file already exists. Overwrite? - + Il file di backup selezionato esiste già. Sovrascriverlo? Starting backup ... - + Avvio del backup... Backup successful. - + Backup completato. Backup failed! - Backup fallito! + Backup non riuscito. @@ -85,48 +85,48 @@ Bootloader installation requires you to provide a copy of the original Sandisk firmware (bin file). This firmware file will be patched and then installed to your player along with the rockbox bootloader. You need to download this file yourself due to legal reasons. Please browse the <a href='http://forums.sandisk.com/sansa/'>Sansa Forums</a> or refer to the <a href='https://www.rockbox.org/manual.shtml'>manual</a> and the <a href='https://www.rockbox.org/wiki/SansaAMS'>SansaAMS</a> wiki page on how to obtain this file.<br/><b>Note:</b> This file is not present on your player and will disappear automatically after installing it.<br/><br/>Press Ok to continue and browse your computer for the firmware file. - + L'installazione del bootloader richiede una copia del firmware originale Sandisk (file BIN). Il firmware verrà modificato e quindi installato sul tuo lettore insieme al bootloader Rockbox. Per motivi legali, devi scaricare manualmente questo file. Consulta i <a href='http://forums.sandisk.com/sansa/'>forum Sansa</a> oppure il <a href='https://www.rockbox.org/manual.shtml'>manuale</a> e la pagina wiki <a href='https://www.rockbox.org/wiki/SansaAMS'>SansaAMS</a> per sapere come ottenerlo.<br/><b>Nota:</b> Il file non è presente sul lettore e verrà eliminato automaticamente dopo l'installazione.<br/><br/>Premi OK per continuare e selezionare il file del firmware sul computer. Downloading bootloader file - + Download del file del bootloader in corso Could not load %1 - + Impossibile caricare %1 No room to insert bootloader, try another firmware version - + Nel firmware non c’è spazio sufficiente per inserire il bootloader. Prova un’altra versione del firmware Patching Firmware... - + Modifica del firmware in corso... Could not open %1 for writing - + Impossibile aprire %1 in scrittura Could not write firmware file - + Impossibile scrivere il file del firmware Success: modified firmware file created - Firmware modificato creato con successo + Firmware modificato creato con successo To uninstall, perform a normal upgrade with an unmodified original firmware - + Per disinstallarlo, esegui un normale aggiornamento con un firmware originale non modificato @@ -134,47 +134,47 @@ Bootloader installation requires you to provide the correct verrsion of the original firmware file. This file will be patched with the Rockbox bootloader and installed to your player. You need to download this file yourself due to legal reasons. Please refer to the <a href='https://www.rockbox.org/wiki/'>rockbox wiki</a> pages on how to obtain this file.<br/>Press Ok to continue and browse your computer for the firmware file. - + Per installare il bootloader devi fornire la versione corretta del firmware originale. Il file verrà modificato con il bootloader Rockbox e installato sul tuo lettore. Per motivi legali, devi scaricarlo manualmente. Consulta le pagine della <a href='https://www.rockbox.org/wiki/'>wiki di Rockbox</a> per sapere come ottenerlo.<br/>Premi OK per continuare e selezionare il file del firmware sul computer. Could not read original firmware file - + Impossibile leggere il file del firmware originale Downloading bootloader file - + Download del file del bootloader in corso Patching file... - + Modifica del file in corso... Patching the original firmware failed - + La modifica del firmware originale non è riuscita Succesfully patched firmware file - + File del firmware modificato correttamente Bootloader successful installed - Bootloader installato con successo + Bootloader installato con successo Patched bootloader could not be installed - + Impossibile installare il bootloader modificato To uninstall, perform a normal upgrade with an unmodified original firmware. - + Per disinstallarlo, esegui un normale aggiornamento con un firmware originale non modificato. @@ -232,42 +232,42 @@ Zip file format detected - + Rilevato il formato ZIP CAB file format detected - + Rilevato il formato CAB Extracting firmware %1 from archive - + Estrazione del firmware %1 dall'archivio Error extracting firmware from archive - + Errore durante l'estrazione del firmware dall'archivio Could not find firmware in archive - + Impossibile trovare il firmware nell'archivio Waiting for system to remount player - + In attesa che il sistema renda nuovamente disponibile il lettore Player remounted - + Il lettore è nuovamente disponibile Timeout on remount - + Il lettore non è tornato disponibile entro il tempo previsto @@ -275,77 +275,77 @@ Bootloader installation requires you to provide a firmware file of the original firmware (HXF file). You need to download this file yourself due to legal reasons. Please refer to the <a href='https://www.rockbox.org/manual.shtml'>manual</a> and the <a href='https://www.rockbox.org/wiki/OndaVX747#Download_and_extract_a_recent_ve'>OndaVX747</a> wiki page on how to obtain this file.<br/>Press Ok to continue and browse your computer for the firmware file. - + L'installazione del bootloader richiede un file del firmware originale (file HXF). Per motivi legali, devi scaricarlo manualmente. Consulta il <a href='https://www.rockbox.org/manual.shtml'>manuale</a> e la pagina wiki <a href='https://www.rockbox.org/wiki/OndaVX747#Download_and_extract_a_recent_ve'>OndaVX747</a> per sapere come ottenerlo.<br/>Premi OK per continuare e selezionare il file del firmware sul computer. Downloading bootloader file - + Download del file del bootloader in corso Could not open firmware file - + Impossibile aprire il file del firmware Could not open bootloader file - + Impossibile aprire il file del bootloader Could not allocate memory - + Impossibile allocare la memoria Could not load firmware file - + Impossibile caricare il file del firmware File is not a valid ChinaChip firmware - + Il file non è un firmware ChinaChip valido Could not find ccpmp.bin in input file - + Impossibile trovare ccpmp.bin nel file di origine Could not open backup file for ccpmp.bin - + Impossibile aprire il file di backup per ccpmp.bin Could not write backup file for ccpmp.bin - + Impossibile scrivere il file di backup per ccpmp.bin Could not load bootloader file - + Impossibile caricare il file del bootloader Could not get current time - + Impossibile ottenere l'ora corrente Could not open output file - + Impossibile aprire il file di destinazione Could not write output file - + Impossibile scrivere il file di destinazione Unexpected error from chinachippatcher - + Errore imprevisto restituito da chinachippatcher @@ -368,7 +368,7 @@ A firmware file is already present on player - + Un file del firmware è già presente sul lettore @@ -378,7 +378,7 @@ Copying modified firmware file failed - + Impossibile copiare il file del firmware modificato @@ -411,7 +411,7 @@ Bootloader installation requires you to provide a firmware file of the original firmware (hex file). You need to download this file yourself due to legal reasons. Please refer to the <a href='https://www.rockbox.org/manual.shtml'>manual</a> and the <a href='https://www.rockbox.org/wiki/IriverBoot#Download_and_extract_a_recent_ve'>IriverBoot</a> wiki page on how to obtain this file.<br/>Press Ok to continue and browse your computer for the firmware file. - L'installazione del bootloader richiede di fornire il file del firmware originale(hex file). Occorre scaricarlo a parte a causa di problemi legali. Fate riferimento al <ahref='https://www.rockbox.org/manual.shtml'>manuale</a> e al <ahref='https://www.rockbox.org/wiki/IriverBoot#Download_and_extract_a_recent_ve'>wiki IriverBoot</a>per ottenere il file.<br/>Premere Ok per continuare e sfogliare le cartelle per cercare il file del firmware. + L'installazione del bootloader richiede un file del firmware originale (file HEX). Per motivi legali, devi scaricarlo manualmente. Consulta il <a href='https://www.rockbox.org/manual.shtml'>manuale</a> e la pagina wiki <a href='https://www.rockbox.org/wiki/IriverBoot#Download_and_extract_a_recent_ve'>IriverBoot</a> per sapere come ottenerlo.<br/>Premi OK per continuare e selezionare il file del firmware sul computer. @@ -436,7 +436,7 @@ Firmware file doesn't match selected player. - Il firmware non è compatibile col player selezionato. + Il firmware non è compatibile con il lettore selezionato. @@ -516,7 +516,7 @@ A firmware file is already present on player - + Un file del firmware è già presente sul lettore @@ -526,12 +526,12 @@ Copying modified firmware file failed - + Impossibile copiare il file del firmware modificato Uninstallation not possible, only installation info removed - + Impossibile disinstallare: sono state rimosse solo le informazioni sull'installazione @@ -584,47 +584,47 @@ Bootloader installation requires you to provide a copy of the original Sandisk firmware (firmware.sb file). This file will be patched with the Rockbox bootloader and installed to your player. You need to download this file yourself due to legal reasons. Please browse the <a href='http://forums.sandisk.com/sansa/'>Sansa Forums</a> or refer to the <a href='https://www.rockbox.org/manual.shtml'>manual</a> and <a href= 'https://www.rockbox.org/wiki/SansaFuzePlus'>SansaFuzePlus</a> wiki page on how to obtain this file.<br/>Press Ok to continue and browse your computer for the firmware file. - + L'installazione del bootloader richiede una copia del firmware originale Sandisk (file firmware.sb). Il file verrà modificato con il bootloader Rockbox e installato sul tuo lettore. Per motivi legali, devi scaricarlo manualmente. Consulta i <a href='http://forums.sandisk.com/sansa/'>forum Sansa</a> oppure il <a href='https://www.rockbox.org/manual.shtml'>manuale</a> e la pagina wiki <a href= 'https://www.rockbox.org/wiki/SansaFuzePlus'>SansaFuzePlus</a> per sapere come ottenerlo.<br/>Premi OK per continuare e selezionare il file del firmware sul computer. Could not read original firmware file - + Impossibile leggere il file del firmware originale Downloading bootloader file - + Download del file del bootloader in corso Patching file... - + Modifica del file in corso... Patching the original firmware failed - + La modifica del firmware originale non è riuscita Succesfully patched firmware file - + File del firmware modificato correttamente Bootloader successful installed - Bootloader installato con successo + Bootloader installato con successo Patched bootloader could not be installed - + Impossibile installare il bootloader modificato To uninstall, perform a normal upgrade with an unmodified original firmware. - + Per disinstallarlo, esegui un normale aggiornamento con un firmware originale non modificato. @@ -659,13 +659,14 @@ Warning: This is a MacPod, Rockbox only runs on WinPods. See https://www.rockbox.org/wiki/IpodConversionToFAT32 - + Attenzione: questo è un MacPod; Rockbox funziona solo sui WinPod. +Consulta https://www.rockbox.org/wiki/IpodConversionToFAT32 - Could not open Ipod in R/W mode - Impossibile accedere all'Ipod in R/W + Could not open iPod in R/W mode + Impossibile accedere all'iPod in R/W @@ -680,12 +681,12 @@ See https://www.rockbox.org/wiki/IpodConversionToFAT32 Bootloader Installation complete. - + Installazione del bootloader completata. Writing log aborted - + Scrittura del registro interrotta @@ -710,27 +711,27 @@ See https://www.rockbox.org/wiki/IpodConversionToFAT32 Error: could not retrieve device name - + Errore: impossibile recuperare il nome del dispositivo Error: no mountpoint specified! - + Errore: nessun punto di montaggio specificato! - Could not open Ipod: permission denied - + Could not open iPod: permission denied + Impossibile aprire l'iPod: accesso negato - Could not open Ipod - impossibile accedere all'Ipod + Could not open iPod + impossibile accedere all'iPod - Error reading partition table - possibly not an Ipod - Errore nel leggere la tabelle delle partizioni - è possibile che non sia un Ipod + Error reading partition table - possibly not an iPod + Errore nel leggere la tabelle delle partizioni - è possibile che non sia un iPod @@ -753,7 +754,7 @@ See https://www.rockbox.org/wiki/IpodConversionToFAT32 A firmware file is already present on player - + Un file del firmware è già presente sul lettore @@ -764,7 +765,7 @@ See https://www.rockbox.org/wiki/IpodConversionToFAT32 Copying modified firmware file failed - + Impossibile copiare il file del firmware modificato @@ -797,77 +798,77 @@ See https://www.rockbox.org/wiki/IpodConversionToFAT32 Bootloader installation requires you to provide a firmware file of the original firmware (bin file). You need to download this file yourself due to legal reasons. Please refer to the <a href='https://www.rockbox.org/manual.shtml'>manual</a> and the <a href='https://www.rockbox.org/wiki/MPIOHD200Port'>MPIOHD200Port</a> wiki page on how to obtain this file.<br/>Press Ok to continue and browse your computer for the firmware file. - + L'installazione del bootloader richiede un file del firmware originale (file BIN). Per motivi legali, devi scaricarlo manualmente. Consulta il <a href='https://www.rockbox.org/manual.shtml'>manuale</a> e la pagina wiki <a href='https://www.rockbox.org/wiki/MPIOHD200Port'>MPIOHD200Port</a> per sapere come ottenerlo.<br/>Premi OK per continuare e selezionare il file del firmware sul computer. Downloading bootloader file - + Download del file del bootloader in corso Could not open the original firmware. - + Impossibile aprire il firmware originale. Could not read the original firmware. - + Impossibile leggere il firmware originale. Loaded firmware file does not look like MPIO original firmware file. - + Il file caricato non sembra essere un firmware originale MPIO. Could not open output file. - + Impossibile aprire il file di destinazione. Could not write output file. - + Impossibile scrivere il file di destinazione. Unknown error number: %1 - + Numero di errore sconosciuto: %1 Could not open downloaded bootloader. - + Impossibile aprire il bootloader scaricato. Place for bootloader in OF file not empty. - + Lo spazio riservato al bootloader nel file del firmware originale (OF) non è vuoto. Could not read the downloaded bootloader. - + Impossibile leggere il bootloader scaricato. Bootloader checksum error. - + Errore di checksum del bootloader. Patching original firmware failed: %1 - + La modifica del firmware originale non è riuscita: %1 Success: modified firmware file created - Firmware modificato creato con successo + Firmware modificato creato con successo To uninstall, perform a normal upgrade with an unmodified original firmware - + Per disinstallarlo, esegui un normale aggiornamento con un firmware originale non modificato @@ -875,149 +876,161 @@ See https://www.rockbox.org/wiki/IpodConversionToFAT32 Could not find mounted iPod. - + Impossibile trovare l'iPod tra i dispositivi disponibili. Downloading bootloader file... - + Download del file del bootloader in corso... Could not make DFU image. - + Impossibile creare l'immagine DFU. Ejecting iPod... - + Rimozione sicura dell'iPod in corso... Action required: Please make sure no programs are accessing files on the device. If ejecting still fails please use your computers eject functionality. - + Azione richiesta: + +Assicurati che nessun programma stia accedendo ai file sul dispositivo. Se la rimozione sicura continua a non riuscire, usa la funzione di rimozione sicura del computer. Device successfully ejected. - + Il dispositivo può essere scollegato in sicurezza. Action required: Quit iTunes application. - + Azione richiesta: + +Chiudi iTunes. iTunes closed. - + iTunes chiuso. Could not suspend iTunesHelper. Stop it using the Task Manager, and try again. - + Impossibile sospendere iTunesHelper. Termina il processo da Gestione attività e riprova. Waiting for HDD spin-down... - + In attesa che il disco rigido si arresti... Waiting for DFU mode... - + In attesa della modalità DFU... Action required: Press and hold SELECT+MENU buttons, after about 12 seconds a new action will require you to release the buttons, DO IT QUICKLY, otherwise the process could fail. - + Azione richiesta: + +Tieni premuti i pulsanti SELECT+MENU. Tra circa 12 secondi ti verrà chiesto di rilasciarli: FALLO SUBITO, altrimenti il processo potrebbe non riuscire. DFU mode detected. - + Rilevata la modalità DFU. Action required: Release SELECT+MENU buttons and wait... - + Azione richiesta: + +Rilascia i pulsanti SELECT+MENU e attendi... Device is not in DFU mode. It seems that the previous required action failed, please try again. - + Il dispositivo non è in modalità DFU. Sembra che l'azione richiesta in precedenza non sia riuscita; riprova. Transfering DFU image... - + Trasferimento dell'immagine DFU in corso... No valid DFU USB driver found. Install iTunes (or the Apple Device Driver) and try again. - + Non è stato trovato alcun driver USB DFU valido. + +Installa iTunes (o il driver del dispositivo Apple) e riprova. Could not transfer DFU image. - + Impossibile trasferire l'immagine DFU. DFU transfer completed. - + Trasferimento DFU completato. Restarting iPod, waiting for remount... - + Riavvio dell'iPod; in attesa che torni disponibile... Action required: Could not remount the device, try to do it manually. If the iPod didn't restart, force a reset by pressing SELECT+MENU buttons for about 5 seconds. If the problem could not be solved then click 'Abort' to cancel. - + Azione richiesta: + +Il dispositivo non è tornato disponibile; prova a ricollegarlo manualmente. Se l'iPod non si è riavviato, forza il riavvio tenendo premuti i pulsanti SELECT+MENU per circa 5 secondi. Se il problema persiste, fai clic su «Interrompi» per annullare. Device remounted. - + Il dispositivo è nuovamente disponibile. Bootloader successfully installed. - + Bootloader installato correttamente. Bootloader successfully uninstalled. - + Bootloader disinstallato correttamente. Could not resume iTunesHelper. - + Impossibile riattivare iTunesHelper. Install aborted by user. - + Installazione interrotta dall'utente. Uninstall aborted by user. - + Disinstallazione interrotta dall'utente. @@ -1093,12 +1106,12 @@ Vedi https://www.rockbox.org/wiki/SansaE200Install Bootloader Installation complete. - + Installazione del bootloader completata. Writing log aborted - + Scrittura del registro interrotta @@ -1118,7 +1131,7 @@ Vedi https://www.rockbox.org/wiki/SansaE200Install Error: could not retrieve device name - + Errore: impossibile recuperare il nome del dispositivo @@ -1146,53 +1159,53 @@ Vedi https://www.rockbox.org/wiki/SansaE200Install Bootloader installation requires you to provide a firmware file of the original firmware (bin file). You need to download this file yourself due to legal reasons. Please refer to the <a href='https://www.rockbox.org/manual.shtml'>manual</a> and the <a href='https://www.rockbox.org/wiki/CowonD2Info'>CowonD2Info</a> wiki page on how to obtain the file.<br/>Press Ok to continue and browse your computer for the firmware file. - + L'installazione del bootloader richiede un file del firmware originale (file BIN). Per motivi legali, devi scaricarlo manualmente. Consulta il <a href='https://www.rockbox.org/manual.shtml'>manuale</a> e la pagina wiki <a href='https://www.rockbox.org/wiki/CowonD2Info'>CowonD2Info</a> per sapere come ottenerlo.<br/>Premi OK per continuare e selezionare il file del firmware sul computer. Downloading bootloader file - + Download del file del bootloader in corso Could not load %1 - + Impossibile caricare %1 Unknown OF file used: %1 - + File del firmware originale (OF) non riconosciuto: %1 Patching Firmware... - + Modifica del firmware in corso... Could not patch firmware - + Impossibile modificare il firmware Could not open %1 for writing - + Impossibile aprire %1 in scrittura Could not write firmware file - + Impossibile scrivere il file del firmware Success: modified firmware file created - Firmware modificato creato con successo + Firmware modificato creato con successo To uninstall, perform a normal upgrade with an unmodified original firmware - + Per disinstallarlo, esegui un normale aggiornamento con un firmware originale non modificato @@ -1200,17 +1213,17 @@ Vedi https://www.rockbox.org/wiki/SansaE200Install Changelog - + Changelog Show on startup - + Mostra all'avvio &Ok - &Ok + &Ok @@ -1223,27 +1236,27 @@ Vedi https://www.rockbox.org/wiki/SansaE200Install No mountpoint given - Nessun mountpoint scelto + Nessun punto di montaggio selezionato Mountpoint does not exist - Mountpoint inesistente + Il punto di montaggio non esiste Mountpoint is not a directory. - Il Mountpoint non è una cartella. + Il punto di montaggio non è una cartella. Mountpoint is not writeable - Il Mountpoint non è scrivibile + Il punto di montaggio non è scrivibile No player selected - Nessun player selezionato + Nessun lettore selezionato @@ -1268,24 +1281,24 @@ Vedi https://www.rockbox.org/wiki/SansaE200Install Showing disabled targets - + Visualizzazione dei dispositivi disabilitati You just enabled showing targets that are marked disabled. Disabled targets are not recommended to end users. Please use this option only if you know what you are doing. - + Hai attivato la visualizzazione dei dispositivi contrassegnati come disabilitati. L'uso di questi dispositivi non è consigliato agli utenti finali. Usa questa opzione solo se sai cosa stai facendo. TTS error - + Errore TTS The selected TTS failed to initialize. You can't use this TTS. - + L'inizializzazione del TTS selezionato non è riuscita. Non puoi utilizzarlo. @@ -1302,61 +1315,63 @@ Vedi https://www.rockbox.org/wiki/SansaE200Install Proxy Detection - + Rilevamento del proxy The System Proxy settings are invalid! Rockbox Utility can't work with this proxy settings. Make sure the system proxy is set correctly. Note that "proxy auto-config (PAC)" scripts are not supported by Rockbox Utility. If your system uses this you need to use manual proxy settings. - + Le impostazioni del proxy di sistema non sono valide! +Rockbox Utility non può funzionare con queste impostazioni. Assicurati che il proxy di sistema sia configurato correttamente. Gli script di configurazione automatica del proxy (PAC) non sono supportati da Rockbox Utility. Se il sistema ne utilizza uno, devi configurare manualmente il proxy. Set Cache Path - + Imposta il percorso della cache %1 (%2 GiB of %3 GiB free) - + %1 (%2 GiB liberi su %3 GiB) Multiple devices have been detected. Please disconnect all players but one and try again. - + Sono stati rilevati più dispositivi. Scollega tutti i lettori tranne uno e riprova. Detected devices: - + Dispositivi rilevati: (unknown) - + (sconosciuto) %1 at %2 - + %1 in %2 Note: detecting connected devices might be ambiguous. You might have less devices connected than listed. In this case it might not be possible to detect your player unambiguously. - + Nota: il rilevamento dei dispositivi collegati può dare risultati ambigui. Potrebbero essere elencati più dispositivi di quelli realmente collegati. In tal caso potrebbe non essere possibile identificare il lettore in modo univoco. Device Detection - + Rilevamento del dispositivo %1 "MacPod" found! -Rockbox needs a FAT formatted Ipod (so-called "WinPod") to run. - +Rockbox needs a FAT formatted iPod (so-called "WinPod") to run. + Rilevato %1 «MacPod»! +Per funzionare, Rockbox richiede un iPod con file system FAT (il cosiddetto «WinPod»). Fatal error @@ -1367,52 +1382,58 @@ Rockbox needs a FAT formatted Ipod (so-called "WinPod") to run. Detected an unsupported player: %1 Sorry, Rockbox doesn't run on your player. - + Rilevato un lettore non supportato: +%1 +Rockbox non è compatibile con questo lettore. TTS configuration invalid - + Configurazione TTS non valida TTS configuration invalid. Please configure TTS engine. - + Configurazione del TTS non valida. +Configura il motore TTS. Could not start TTS engine. - + Impossibile avviare il motore TTS. Could not start TTS engine. - + Impossibile avviare il motore TTS. + Please configure TTS engine. - + +Configura il motore TTS. Rockbox Utility Voice Test - + Test vocale di Rockbox Utility Could not voice test string. - + Impossibile pronunciare la frase di prova. Could not voice test string. - + Impossibile pronunciare la frase di prova. + @@ -1423,30 +1444,34 @@ Please configure TTS engine. %1 in MTP mode found! You need to change your player to MSC mode for installation. - + Rilevato %1 in modalità MTP! +Per l'installazione devi impostare il lettore in modalità MSC. Could not detect a Mountpoint. Select your Mountpoint manually. - Impossibile individuare un Mountpoint.Selezionare un Mountpoint manualmente. + Impossibile rilevare un punto di montaggio. +Seleziona manualmente il punto di montaggio. Could not detect a device. Select your device and Mountpoint manually. - Impossibile individuare il dispositivo.Selezionare il dispositivo e il Mountpoint manualmente. + Impossibile rilevare il dispositivo. +Seleziona manualmente il dispositivo e il punto di montaggio. The player contains an incompatible filesystem. Make sure you selected the correct mountpoint and the player is set up to use a filesystem compatible with Rockbox. - + Il lettore utilizza un file system incompatibile. +Assicurati di aver selezionato il punto di montaggio corretto e che il lettore utilizzi un file system compatibile con Rockbox. An unknown error occured during player detection. - + Si è verificato un errore sconosciuto durante il rilevamento del lettore. @@ -1499,17 +1524,17 @@ Make sure you selected the correct mountpoint and the player is set up to use a &Select your audio player - &Seleziona il tuo player audio + &Seleziona il lettore audio &Refresh - &Aggiorna + &Aggiorna Show disabled targets - + Mostra i dispositivi disabilitati @@ -1564,7 +1589,7 @@ Make sure you selected the correct mountpoint and the player is set up to use a Show - + Mostra @@ -1618,7 +1643,7 @@ Make sure you selected the correct mountpoint and the player is set up to use a &TTS && Encoder - &TTS &&Encoder + &TTS && codificatore @@ -1628,12 +1653,12 @@ Make sure you selected the correct mountpoint and the player is set up to use a &Select TTS Engine - &Scegli engine TTS + &Scegli motore TTS Configure TTS Engine - Configura engine TTS + Configura motore TTS @@ -1649,17 +1674,17 @@ Make sure you selected the correct mountpoint and the player is set up to use a Test TTS - + Test TTS &Use string corrections for TTS - + &Usa le correzioni di pronuncia per il TTS Encoder Engine - Engine encoder + Motore di codifica @@ -1669,7 +1694,7 @@ Make sure you selected the correct mountpoint and the player is set up to use a encoder name - nome encoder + Nome del codificatore @@ -1679,7 +1704,7 @@ Make sure you selected the correct mountpoint and the player is set up to use a &Cancel - &Cancel + &Annulla @@ -1696,22 +1721,22 @@ Make sure you selected the correct mountpoint and the player is set up to use a Create Voice File - Crea file voce + Crea file vocale Select the Language you want to generate a voicefile for: - seleziona la lingua per la quale vuoi generare un file voce: + Seleziona la lingua per la quale vuoi generare un file vocale: TTS: - + TTS: Silence threshold - + Soglia del silenzio @@ -1749,18 +1774,18 @@ Make sure you selected the correct mountpoint and the player is set up to use a TTS error - + Errore TTS The selected TTS failed to initialize. You can't use this TTS. - + L'inizializzazione del TTS selezionato non è riuscita. Non puoi utilizzarlo. Engine: <b>%1</b> - + Motore: <b>%1</b> @@ -1793,7 +1818,7 @@ Make sure you selected the correct mountpoint and the player is set up to use a Select executable - + Seleziona il file eseguibile @@ -1801,12 +1826,12 @@ Make sure you selected the correct mountpoint and the player is set up to use a Path to Encoder: - Percorso dell'Encoder: + Percorso del codificatore: Encoder options: - Opzioni per l'Encoder: + Opzioni del codificatore: @@ -1815,22 +1840,22 @@ Make sure you selected the correct mountpoint and the player is set up to use a LAME - + LAME Volume - + Volume Quality - + Qualità Could not find libmp3lame! - + Impossibile trovare la libreria libmp3lame! @@ -1838,22 +1863,22 @@ Make sure you selected the correct mountpoint and the player is set up to use a Volume: - Volume: + Volume: Quality: - Qualità: + Qualità: Complexity: - Complessità: + Complessità: Use Narrowband: - Usa Narrowband: + Usa la banda stretta: @@ -1862,18 +1887,18 @@ Make sure you selected the correct mountpoint and the player is set up to use a File - File + File Version - Versione + Versione Loading, please wait ... - + Caricamento in corso, attendi... @@ -1881,17 +1906,17 @@ Make sure you selected the correct mountpoint and the player is set up to use a Info - info + Informazioni Currently installed packages.<br/><b>Note:</b> if you manually installed packages this might not be correct! - Pacchetti correntemente installati.<br/><b>Nota:</b>se si sono installati dei pacchetti manualmente potrebbero non essere visti! + Pacchetti attualmente installati.<br/><b>Nota:</b> se hai installato manualmente alcuni pacchetti, queste informazioni potrebbero non essere corrette. Package - + Pacchetto @@ -1904,7 +1929,7 @@ Make sure you selected the correct mountpoint and the player is set up to use a Generate for files - + Crea file vocali .talk per i file @@ -1914,12 +1939,12 @@ Make sure you selected the correct mountpoint and the player is set up to use a Generate for folders - + Crea file vocali .talk per le cartelle Recurse into folders - + Includi le sottocartelle @@ -1929,17 +1954,17 @@ Make sure you selected the correct mountpoint and the player is set up to use a Ignore files - + Ignora i file Skip existing - + Salta i file vocali .talk esistenti Select folders for Talkfile generation (Ctrl for multiselect) - + Seleziona le cartelle in cui creare i file vocali .talk (tieni premuto Ctrl per selezioni multiple) @@ -1949,7 +1974,7 @@ Make sure you selected the correct mountpoint and the player is set up to use a Strip Extensions - + Non pronunciare le estensioni @@ -1959,7 +1984,7 @@ Make sure you selected the correct mountpoint and the player is set up to use a &Cancel - &Cancella + &Annulla @@ -1967,22 +1992,22 @@ Make sure you selected the correct mountpoint and the player is set up to use a Empty selection - + Selezione vuota No files or folders selected. Please select files or folders first. - + Non hai selezionato file o cartelle. Selezionali prima di continuare. TTS error - + Errore TTS The selected TTS failed to initialize. You can't use this TTS. - + L'inizializzazione del TTS selezionato non è riuscita. Non puoi utilizzarlo. @@ -2048,12 +2073,12 @@ Make sure you selected the correct mountpoint and the player is set up to use a Creating output path failed - + Impossibile creare il percorso di destinazione Error during CAB operation - + Errore durante l'estrazione dall'archivio CAB @@ -2061,27 +2086,27 @@ Make sure you selected the correct mountpoint and the player is set up to use a Stable (Retired) - + Stabile (non più mantenuto) Unusable - + Non utilizzabile Unstable - + Instabile Stable - + Stabile Unknown - + Sconosciuto @@ -2108,7 +2133,7 @@ Make sure you selected the correct mountpoint and the player is set up to use a Save Log - + Salva il registro @@ -2126,12 +2151,12 @@ Make sure you selected the correct mountpoint and the player is set up to use a &Ok - + &OK Save system trace log - + Salva il registro diagnostico del sistema @@ -2141,67 +2166,67 @@ Make sure you selected the correct mountpoint and the player is set up to use a LTR This string is used to indicate the writing direction. Translate it to "RTL" (without quotes) for RTL languages. Anything else will get treated as LTR language. - + LTR (unknown vendor name) - + (produttore sconosciuto) (unknown product name) - + (modello sconosciuto) Before Bootloader installation begins, Please check the following: - + Prima di iniziare l'installazione del bootloader, verifica quanto segue: <li>Ensure your SD card is formatted as FAT. exFAT is <i>not</i> supported. You can reformat using the Original Firmware on your player if need be. It is located under (System Settings --> Reset --> Format TF Card).</li><li>Please use a quality SD card from a reputable source. The SD cards that come bundled with players are often of substandard quality and may cause issues.</li> - + <li>Assicurati che la scheda SD sia formattata con il file system FAT. exFAT <i>non</i> è supportato. Se necessario, puoi riformattarla tramite il firmware originale del lettore, usando la voce System Settings --> Reset --> Format TF Card.</li><li>Usa una scheda SD di qualità e di una marca affidabile. Le schede SD fornite con i lettori sono spesso di scarsa qualità e possono causare problemi.</li> Bootloader installation is almost complete. Installation <b>requires</b> you to perform the following steps manually: - l'installazione del bootloader è quasi completa. Per continuare <b>occorre</b> che tu faccia le seguenti operazioni manualmente: + L'installazione del bootloader è quasi completa. Per completarla, <b>devi</b> eseguire manualmente le seguenti operazioni: <li>Safely remove your player.</li> - <li>Rimuovi il tuo player in maniera sicura.</li> + <li>Esegui la rimozione sicura del lettore.</li> <li>Reboot your player into the original firmware.</li><li>Perform a firmware upgrade using the update functionality of the original firmware. Please refer to your player's manual on details.<br/><b>Important:</b> updating the firmware is a critical process that must not be interrupted. <b>Make sure the player is charged before starting the firmware update process.</b></li><li>After the firmware has been updated reboot your player.</li> - + <li>Riavvia il lettore con il firmware originale.</li><li>Aggiorna il firmware usando la funzione di aggiornamento del firmware originale. Per i dettagli, consulta il manuale del lettore.<br/><b>Importante:</b> l'aggiornamento del firmware è un processo critico che non deve essere interrotto. <b>Assicurati che il lettore sia carico prima di iniziare.</b></li><li>Al termine dell'aggiornamento, riavvia il lettore.</li> <li>Remove any previously inserted microSD card</li> - + <li>Rimuovi l'eventuale scheda microSD inserita</li> <li>Disconnect your player. The player will reboot and perform an update of the original firmware. Please refer to your players manual on details.<br/><b>Important:</b> updating the firmware is a critical process that must not be interrupted. <b>Make sure the player is charged before disconnecting the player.</b></li><li>After the firmware has been updated reboot your player.</li> - + <li>Scollega il lettore. Il lettore si riavvierà e aggiornerà il firmware originale. Per i dettagli, consulta il manuale del lettore.<br/><b>Importante:</b> l'aggiornamento del firmware è un processo critico che non deve essere interrotto. <b>Assicurati che il lettore sia carico prima di scollegarlo.</b></li><li>Al termine dell'aggiornamento, riavvia il lettore.</li> <li>Turn the player off</li><li>Insert the charger</li> - <li>Spegni il player</li><li>Inserisci il caricabatterie</li> + <li>Spegni il lettore</li><li>Collega il caricabatterie</li> <li>Unplug USB and power adaptors</li><li>Hold <i>Power</i> to turn the player off</li><li>Toggle the battery switch on the player</li><li>Hold <i>Power</i> to boot into Rockbox</li> - <li>Stacca il cavo USB</li><li>Tieni premuto il tasto<i>Power</i>per spegnere il player</li><li>Sposta l'interruttore della batteria sul player</li><li>Tieni premuto <i>Power</i> per avviare Rockbox</li> + <li>Scollega il cavo USB e l'alimentatore</li><li>Tieni premuto <i>Power</i> per spegnere il lettore</li><li>Sposta l'interruttore della batteria sul lettore</li><li>Tieni premuto <i>Power</i> per avviare Rockbox</li> <p><b>Note:</b> You can safely install other parts first, but the above steps are <b>required</b> to finish the installation!</p> - <p><b>Note:</b>Puoi installare in tutta sicurezza prima le altre componenti, ma i passi precedenti <b>sono indispensabili</b>per concludere l'installazione!</p> + <p><b>Nota:</b> puoi prima installare in sicurezza gli altri componenti, ma per completare l'installazione <b>devi</b> eseguire le operazioni indicate sopra.</p> @@ -2209,7 +2234,7 @@ Make sure you selected the correct mountpoint and the player is set up to use a ZIP/UNZIP API error %1 - + Errore dell'API ZIP/UNZIP: %1 @@ -2222,7 +2247,7 @@ Make sure you selected the correct mountpoint and the player is set up to use a Libraries used - + Librerie utilizzate @@ -2248,22 +2273,22 @@ Make sure you selected the correct mountpoint and the player is set up to use a Wine detected! - + Wine rilevato! It seems you are trying to run this program under Wine. Please don't do this, running under Wine will fail. Use the native Linux binary instead. - + Sembra che tu stia tentando di eseguire questo programma con Wine. Non farlo: Rockbox Utility non funziona con Wine. Usa invece il programma nativo per Linux. Downloading build information, please wait ... - + Download delle informazioni sulle build in corso, attendi... Certificate error - + Errore del certificato @@ -2275,23 +2300,31 @@ Valid since: %4 Valid until: %5 Temporarily trust certificate? - + %1 + +Emittente: %2 +Soggetto: %3 +Valido da: %4 +Valido fino a: %5 + +Vuoi considerare temporaneamente attendibile questo certificato? Can't get version information! - + Impossibile ottenere informazioni sulla versione! Can't get version information. Network error: %1. Please check your network and proxy settings. - + Impossibile ottenere informazioni sulla versione. +Errore di rete: %1. Controlla le impostazioni di rete e del proxy. Download build information finished. - + Download delle informazioni sulle build completato. Confirm Installation @@ -2327,17 +2360,17 @@ Network error: %1. Please check your network and proxy settings. No uninstall method for this target known. - + Nessun metodo di disinstallazione noto per questo dispositivo. New version of Rockbox Utility available. - + Disponibile una nuova versione di Rockbox Utility. Rockbox Utility is up to date. - + Rockbox Utility è aggiornata. @@ -2347,22 +2380,22 @@ Network error: %1. Please check your network and proxy settings. Rockbox Utility can not uninstall the bootloader on your player. Please perform a firmware update using your player vendors firmware update process. - + Rockbox Utility non può disinstallare il bootloader sul tuo lettore. Aggiorna il firmware seguendo la procedura fornita dal produttore del lettore. Important: make sure to boot your player into the original firmware before using the vendors firmware update process. - + Importante: assicurati di avviare il lettore con il firmware originale prima di seguire la procedura di aggiornamento del produttore. No Rockbox bootloader found. - + Non è stato trovato alcun bootloader Rockbox. Do you really want to install Rockbox Utility to your player? After installation you can run it from the players hard drive. - Vuoi installare la Rockbox utility nel tuo player ? Dopo l'installazione potrai avviarla dal disco del tuo player. + Vuoi davvero installare Rockbox Utility sul lettore? Dopo l'installazione potrai avviarla dalla memoria del lettore. @@ -2397,37 +2430,37 @@ Network error: %1. Please check your network and proxy settings. Checking for update ... - + Verifica dell'aggiornamento in corso... Rockbox Utility Update available - + Disponibile un aggiornamento di Rockbox Utility <b>New Rockbox Utility version available.</b><br><br>You are currently using version %1. Get version %2 at <a href='%3'>%3</a> - + <b>Disponibile una nuova versione di Rockbox Utility.</b><br><br>Stai utilizzando la versione %1. Scarica la versione %2 da <a href='%3'>%3</a> Device ejected - + Rimozione sicura completata Device successfully ejected. You may now disconnect the player from the PC. - + Il dispositivo può essere scollegato in sicurezza. Ora puoi scollegare il lettore dal computer. Ejecting failed - + Rimozione sicura non riuscita Ejecting the device failed. Please make sure no programs are accessing files on the device. If ejecting still fails please use your computers eject funtionality. - + La rimozione sicura del dispositivo non è riuscita. Assicurati che nessun programma stia accedendo ai file sul dispositivo. Se il problema persiste, usa la funzione di rimozione sicura del computer. @@ -2480,23 +2513,23 @@ Network error: %1. Please check your network and proxy settings. Install Talk files - Installa file Talk + Crea file vocali .talk <b>Create Talk Files</b><br/>Talkfiles are needed to let Rockbox speak File and Foldernames - + <b>Crea file vocali .talk</b><br/>I file vocali .talk consentono a Rockbox di pronunciare i nomi dei file e delle cartelle Create Voice files - Crea file Voice + Crea file vocale <b>Create Voice file</b><br/>Voice files are needed to make Rockbox speak the user interface. Speaking is enabled by default, so if you installed the voice file Rockbox will speak. - + <b>Crea il file vocale</b><br/>Il file vocale consente a Rockbox di pronunciare le voci dell'interfaccia. La sintesi vocale è abilitata per impostazione predefinita: se installi il file vocale, Rockbox parlerà. @@ -2512,27 +2545,27 @@ Network error: %1. Please check your network and proxy settings. mountpoint unknown or invalid - + punto di montaggio sconosciuto o non valido Mountpoint: - + Punto di montaggio: device unknown or invalid - + dispositivo sconosciuto o non valido Device: - + Dispositivo: Backup && &Uninstallation - + Backup e &disinstallazione @@ -2547,17 +2580,17 @@ Network error: %1. Please check your network and proxy settings. <b>Uninstall Rockbox from your audio player.</b><br/>This will leave the bootloader in place (you need to remove it manually). - <b>Disinstallare Rockbox dal player audio.</b><br/>Questo lascerà il bootloader al suo posto (si dovrà rimuoverlo manualmente). + <b>Disinstalla Rockbox dal lettore audio.</b><br/>Il bootloader non verrà rimosso: devi rimuoverlo manualmente. Backup - Backup + Backup <html><head/><body><p><span style=" font-weight:600;">Backup current installation.</span></p><p>Create a backup by archiving the contents of the Rockbox installation folder.</p></body></html> - + <html><head/><body><p><span style=" font-weight:600;">Esegui il backup dell'installazione corrente.</span></p><p>Crea un backup archiviando il contenuto della cartella di installazione di Rockbox.</p></body></html> &Manual @@ -2580,7 +2613,7 @@ Network error: %1. Please check your network and proxy settings. System &Trace - + &Diagnostica di sistema @@ -2590,12 +2623,12 @@ Network error: %1. Please check your network and proxy settings. Welcome to Rockbox Utility, the installation and housekeeping tool for Rockbox. - + Benvenuto in Rockbox Utility, lo strumento di installazione e manutenzione di Rockbox. Rockbox Logo - + Logo di Rockbox @@ -2605,7 +2638,7 @@ Network error: %1. Please check your network and proxy settings. Install Rockbox Utility on player - Installa l'utility Rockbox sul tuo player + Installa Rockbox Utility sul lettore @@ -2641,7 +2674,7 @@ Network error: %1. Please check your network and proxy settings. &Eject - + Rimozione &sicura @@ -2686,12 +2719,12 @@ Network error: %1. Please check your network and proxy settings. &Install Voice File - &Installa file Voice + &Installa file vocale Create &Talk Files - &Crea file Talk + &Crea file vocali .talk @@ -2726,12 +2759,12 @@ Network error: %1. Please check your network and proxy settings. Create &Voice File - Crea &file Voce + Crea &file vocale Create Voice File - Crea file Voce + Crea file vocale @@ -2741,7 +2774,7 @@ Network error: %1. Please check your network and proxy settings. Show &Changelog - + Mostra il &changelog @@ -2749,265 +2782,265 @@ Network error: %1. Please check your network and proxy settings. Selective Installation - + Installazione selettiva Rockbox version to install - + Versione di Rockbox da installare Version information not available yet. - + Le informazioni sulla versione non sono ancora disponibili. Rockbox components to install - + Componenti di Rockbox da installare &Bootloader - + &Bootloader Some plugins require additional data files. - + Alcuni plugin richiedono file di dati aggiuntivi. Install prerendered voice file. - + Installa un file vocale già pronto. Plugin Data - + Dati dei Plugin &Manual - &Manuale + &Manuale &Voice File - + File &vocale The main Rockbox firmware. - + Il firmware principale di Rockbox. Fonts - + Font &Rockbox - + &Rockbox Additional fonts for the User Interface. - + Font aggiuntivi per l'interfaccia utente. The bootloader is required for starting Rockbox. Only necessary for first time install. - + Il bootloader serve per avviare Rockbox. È necessario solo durante la prima installazione. Customize - + Personalizza Themes - + Temi Themes allow adjusting the user interface of Rockbox. Use "Customize" to select themes. - + I temi consentono di modificare l'interfaccia utente di Rockbox. Usa «Personalizza» per selezionarli. Save a copy of the manual on the player. - + Salva una copia del manuale sul lettore. &Install - &Installa + &Installa This is the latest stable release available. - + Questa è l'ultima versione stabile disponibile. This will eventually become the next Rockbox version. Install it to help testing. - + Questa diventerà la prossima versione di Rockbox. Installala per contribuire ai test. Stable Release (Version %1) - + Versione stabile (%1) Development Version (Revison %1) - + Versione di sviluppo (revisione %1) Release Candidate (Revison %1) - + Release candidate (revisione %1) The development version is updated on every code change. - + La versione di sviluppo viene aggiornata a ogni modifica del codice. Daily updated development version. - + Versione di sviluppo aggiornata ogni giorno. Not available for the selected version - + Non disponibile per la versione selezionata Daily Build (%1) - + Daily build (%1) The selected player doesn't need a bootloader. - + Il lettore selezionato non richiede un bootloader. The bootloader is required for starting Rockbox. Installation of the bootloader is only necessary on first time installation. - + Il bootloader è necessario per avviare Rockbox, ma va installato soltanto la prima volta. Mountpoint is wrong - + Il punto di montaggio non è corretto No install method known. - Nessun metodo di installazione conosciuto. + Nessun metodo di installazione disponibile. Bootloader detected - Trovato bootloader + Bootloader rilevato Bootloader already installed. Do you want to reinstall the bootloader? - Bootloader già installato. Vuoi reinstallarlo? + Bootloader già installato. Vuoi reinstallarlo? Bootloader installation skipped - Installazione bootloader saltata + Installazione del bootloader ignorata Create Bootloader backup - Crea un backup del bootloader + Crea il backup del bootloader You can create a backup of the original bootloader file. Press "Yes" to select an output folder on your computer to save the file to. The file will get placed in a new folder "%1" created below the selected folder. Press "No" to skip this step. - Puoi creare un backup del bootloader originale. Premi "Si" e seleziona una cartella sul tuo pc dove salvare il file. Il file verrà salvato in una nuova cartella "%1" all'interno della cartella selezionata. -Premi "No" per saltare questo passo. + Puoi creare una copia di backup del file originale del bootloader. Premi "Sì" e seleziona sul computer la cartella in cui salvarla. Il file verrà inserito in una nuova cartella "%1", creata all'interno della cartella selezionata. +Premi "No" per saltare questo passaggio. Browse backup folder - Scegli la cartella per il backup + Scegli la cartella per il backup Prerequisites - Prerequisiti + Prerequisiti Bootloader installation aborted - Installazione bootloader interrotta + Installazione del bootloader interrotta Bootloader files (%1) - + File del bootloader (%1) All files (*) - + Tutti i file (*) Select firmware file - Selezionare il file del firmware + Seleziona il file del firmware Error opening firmware file - Errore nell'aprire il file del firmware + Errore durante l'apertura del file del firmware Error reading firmware file - + Errore durante la lettura del file del firmware Backup error - Errore durante il backup + Errore durante il backup Could not create backup file. Continue? - Impossibile creare un backup. Continuare comunque? + Impossibile creare un backup. Vuoi continuare comunque? Manual steps required - Richiesto intervento manuale + Sono necessari passaggi manuali Your installation doesn't require any plugin data files, skipping. - + L'installazione non richiede file di dati per i plugin: questo passaggio verrà saltato. Continue with installation? - + Vuoi continuare con l'installazione? Really continue? - Vuoi continuare veramente? + Vuoi davvero continuare? Aborted! @@ -3020,7 +3053,7 @@ Premi "No" per saltare questo passo. Save system trace log - + Salva il registro diagnostico del sistema @@ -3028,32 +3061,32 @@ Premi "No" per saltare questo passo. System Trace - + Diagnostica di sistema System State trace - + Informazioni diagnostiche sullo stato del sistema &Close - + &Chiudi &Save - + &Salva &Refresh - &Aggiorna + &Aggiorna Save &previous - + Salva &precedente @@ -3066,7 +3099,7 @@ Premi "No" per saltare questo passo. <b>Username</b><br/>%1<hr/> - <b>Nome utente<7b><br/>%1<hr/> + <b>Nome utente</b><br/>%1<hr/> @@ -3091,32 +3124,32 @@ Premi "No" per saltare questo passo. Mountpoint - + Punto di montaggio Label - + Etichetta Free - + Spazio libero Total - + Spazio totale Type - + Tipo <tr><td>%1</td><td>%4</td><td>%2 GiB</td><td>%3 GiB</td><td>%5</td></tr> - + <tr><td>%1</td><td>%4</td><td>%2 GiB</td><td>%3 GiB</td><td>%5</td></tr> @@ -3142,27 +3175,27 @@ Premi "No" per saltare questo passo. Guest - Ospite + Utente guest Admin - Amministratore + Utente amministratore User - Utente + Utente Error - Errore + Errore (no description available) - (nessuna descrizione disponibile) + (nessuna descrizione disponibile) @@ -3170,52 +3203,52 @@ Premi "No" per saltare questo passo. Espeak TTS Engine - + Motore TTS eSpeak Espeak-ng TTS Engine - + Motore TTS eSpeak NG Mimic TTS Engine - + Motore TTS Mimic Flite TTS Engine - + Motore TTS Flite Swift TTS Engine - + Motore TTS Swift SAPI4 TTS Engine - + Motore TTS SAPI4 SAPI5 TTS Engine - + Motore TTS SAPI5 MS Speech Platform - + Microsoft Speech Platform Festival TTS Engine - + Motore TTS Festival OS X System Engine - + Motore TTS di sistema di OS X @@ -3223,27 +3256,27 @@ Premi "No" per saltare questo passo. Voice: - Voce: + Voce: Speed (words/min): - + Velocità (parole/min): Pitch (0 for default): - + Tono (0 per impostazione predefinita): Could not voice string - + Impossibile vocalizzare il testo Could not convert intermediate file - + Impossibile convertire il file intermedio @@ -3284,7 +3317,7 @@ Premi "No" per saltare questo passo. Festival could not be started - + Impossibile avviare Festival @@ -3322,12 +3355,12 @@ Premi "No" per saltare questo passo. Could not copy the SAPI script - + Impossibile copiare lo script SAPI Could not start SAPI process - + Impossibile avviare il processo SAPI @@ -3340,37 +3373,37 @@ Premi "No" per saltare questo passo. Talk file creation aborted - Creazione del TalkFile interrotta + Creazione dei file vocali .talk interrotta Copying Talkfiles... - + Copia dei file vocali .talk in corso... File copy aborted - + Copia del file interrotta Cleaning up... - + Pulizia in corso... Finished - + Terminato Finished creating Talk files - Creazione Talkfile conclusa + Creazione dei file vocali .talk completata Starting Talk file generation for folder %1 - + Avvio della generazione dei file vocali .talk per la cartella %1 @@ -3383,54 +3416,54 @@ Premi "No" per saltare questo passo. Starting TTS Engine - + Avvio del motore TTS Init of TTS engine failed - + Inizializzazione del motore TTS non riuscita Starting Encoder Engine - + Avvio del codificatore Init of Encoder engine failed - + Inizializzazione del codificatore non riuscita Voicing entries... - + Vocalizzazione di tutti gli elementi in corso... Encoding files... - Codififca file in corso... + Codifica dei file in corso... Voicing aborted - + Vocalizzazione interrotta Voicing of %1 failed: %2 - Sintesi vocale di %1 fallita: %2 + Vocalizzazione di %1 non riuscita: %2 Encoding aborted - + Codifica interrotta Encoding of %1 failed - Codifica di %1 fallita + Codifica di %1 non riuscita @@ -3468,7 +3501,7 @@ Premi "No" per saltare questo passo. &Cancel - &annulla + &Annulla @@ -3509,8 +3542,8 @@ Please check your network and proxy settings. Download size %L1 kiB (%n item(s)) - Grandezza download %L1 kiB (%n oggetti) - + Dimensione del download: %L1 kiB (%n elemento) + Dimensione del download: %L1 kiB (%n elementi) @@ -3563,7 +3596,7 @@ HTTP response code: %1 Select - + Seleziona @@ -3573,7 +3606,7 @@ HTTP response code: %1 No themes selected, skipping - + Nessun tema selezionato, questo passaggio viene saltato @@ -3645,12 +3678,12 @@ HTTP response code: %1 Uninstalling %1... - + Disinstallazione di %1 in corso... Could not delete %1 - + Impossibile eliminare %1 @@ -3664,18 +3697,18 @@ HTTP response code: %1 <li>Permissions insufficient for bootloader installation. Administrator priviledges are necessary.</li> - <li>Permessi insufficenti per installare il bootloader. + <li>I permessi per installare il bootloader sono insufficienti. Sono necessari i privilegi di amministratore.</li> <li>Target mismatch detected.<br/>Installed target: %1<br/>Selected target: %2.</li> - + <li>Rilevata una mancata corrispondenza del modello.<br/>Modello installato: %1<br/>Modello selezionato: %2.</li> Problem detected: - Problema individuato: + Problema individuato: @@ -3683,7 +3716,7 @@ Sono necessari i privilegi di amministratore.</li> Starting Voicefile generation - Generazione del Voicefile in corso + Creazione del file vocale in corso... @@ -3693,37 +3726,37 @@ Sono necessari i privilegi di amministratore.</li> Extracted voice corrections file from installation - + File delle correzioni di pronuncia estratto dall'installazione Using internal voice corrections file - + Utilizzo del file interno delle correzioni di pronuncia Extracted language enumeration file from installation - + File con l'elenco delle stringhe della lingua estratto dall'installazione Extracted voice strings from installation - + Stringhe vocali estratte dall'installazione Extracted voice strings incompatible - + Le stringhe vocali estratte sono incompatibili Could not retrieve strings from installation, downloading - + Impossibile recuperare le stringhe dall'installazione, le scarico Downloading voice info... - + Scaricamento delle informazioni vocali in corso... @@ -3753,22 +3786,22 @@ Sono necessari i privilegi di amministratore.</li> Reading strings... - + Lettura delle stringhe in corso... Creating voicefiles... - + Creazione dei file vocali in corso... Cleaning up... - + Pulizia in corso... Finished - + Terminato @@ -3829,29 +3862,29 @@ Sono necessari i privilegi di amministratore.</li> Extraction failed! - + Estrazione non riuscita! Not enough disk space! Aborting. - Spazio du disco insufficente! Interruzione in corso. + Spazio su disco insufficiente! Operazione interrotta. Package installation finished successfully. - + L'installazione del pacchetto è stata completata correttamente. Download error: received HTTP error %1 %2 - Errore durante il download: errore HTTP %1. {1 -%2?} + Errore durante il download: errore HTTP %1 +%2 Download finished (cache used). - Download concluso (cache usata). + Download terminato (è stata utilizzata la cache). @@ -3874,17 +3907,17 @@ Sono necessari i privilegi di amministratore.</li> Creating output path failed - + Creazione del percorso di destinazione non riuscita Creating output file failed - + Creazione del file di destinazione non riuscita Error during Zip operation - + Errore durante l'operazione ZIP @@ -3902,7 +3935,7 @@ Sono necessari i privilegi di amministratore.</li> Installer and housekeeping utility for the Rockbox open source digital audio player firmware.<br/>© The Rockbox Team.<br/>Released under the GNU General Public License v2.<br/>Uses icons by the <a href="http://tango.freedesktop.org/">Tango Project</a>.<br/><center><a href="https://www.rockbox.org">https://www.rockbox.org</a></center> - + Utility di installazione e manutenzione per Rockbox, firmware open source per lettori audio digitali.<br/>© The Rockbox Team.<br/>Distribuito secondo i termini della GNU General Public License v2.<br/>Usa le icone del <a href="http://tango.freedesktop.org/">Tango Project</a>.<br/><center><a href="https://www.rockbox.org">https://www.rockbox.org</a></center> @@ -3917,7 +3950,7 @@ Sono necessari i privilegi di amministratore.</li> L&ibraries - + L&ibrerie diff --git a/utils/rbutilqt/lang/rbutil_ja.ts b/utils/rbutilqt/lang/rbutil_ja.ts index 247edde3a3..6e4826a7c2 100644 --- a/utils/rbutilqt/lang/rbutil_ja.ts +++ b/utils/rbutilqt/lang/rbutil_ja.ts @@ -660,7 +660,7 @@ See https://www.rockbox.org/wiki/IpodConversionToFAT32 - Could not open Ipod in R/W mode + Could not open iPod in R/W mode iPod に読み書きできるようにアクセスすることができませんでした @@ -700,12 +700,12 @@ See https://www.rockbox.org/wiki/IpodConversionToFAT32 - Could not open Ipod: permission denied + Could not open iPod: permission denied iPod にアクセスすることができませんでした: アクセス権限がありません - Could not open Ipod + Could not open iPod iPod にアクセスできませんでした @@ -735,7 +735,7 @@ See https://www.rockbox.org/wiki/IpodConversionToFAT32 - Error reading partition table - possibly not an Ipod + Error reading partition table - possibly not an iPod パーティションテーブルの読み込みのエラー - iPod ではない可能性があります @@ -1412,7 +1412,7 @@ Rockbox Utility は、このプロキシの設定では動作できません。 %1 "MacPod" found! -Rockbox needs a FAT formatted Ipod (so-called "WinPod") to run. +Rockbox needs a FAT formatted iPod (so-called "WinPod") to run. %1 Macintosh 専用の iPod が見つかりました。 Rockbox を実行するには、FAT 形式でフォーマットされた iPod ("WinMad")が必要です。 diff --git a/utils/rbutilqt/lang/rbutil_ko.ts b/utils/rbutilqt/lang/rbutil_ko.ts index c1223623fd..8347c9b81e 100644 --- a/utils/rbutilqt/lang/rbutil_ko.ts +++ b/utils/rbutilqt/lang/rbutil_ko.ts @@ -663,7 +663,7 @@ https://www.rockbox.org/wiki/IpodConversionToFAT32 참고 - Could not open Ipod in R/W mode + Could not open iPod in R/W mode R/W 모드에서 iPod을 열 수 없음 @@ -703,13 +703,13 @@ https://www.rockbox.org/wiki/IpodConversionToFAT32 참고 - Could not open Ipod: permission denied + Could not open iPod: permission denied iPod을 열 수 없음: 권한이 거부됨 - Could not open Ipod - Ipod을 열 수 없음 + Could not open iPod + iPod을 열 수 없음 @@ -744,8 +744,8 @@ See https://www.rockbox.org/wiki/IpodConversionToFAT32 - Error reading partition table - possibly not an Ipod - 파티션 테이블을 읽는 중 오류 발생 - 아마도 Ipod이 아닐 수도 있음 + Error reading partition table - possibly not an iPod + 파티션 테이블을 읽는 중 오류 발생 - 아마도 iPod이 아닐 수도 있음 @@ -1260,7 +1260,7 @@ Select your device and Mountpoint manually. %1 "MacPod" found! -Rockbox needs a FAT formatted Ipod (so-called "WinPod") to run. +Rockbox needs a FAT formatted iPod (so-called "WinPod") to run. @@ -1441,7 +1441,7 @@ Rockbox Utility can't work with this proxy settings. Make sure the system p %1 "MacPod"를 찾았습니다! -Rockbox needs a FAT formatted Ipod (so-called "WinPod") to run. +Rockbox needs a FAT formatted iPod (so-called "WinPod") to run. %1 "MacPod" gefunden! 록박스를 실행하려면 FAT 형식의 아이팟(소위 "WinPod")이 필요합니다. diff --git a/utils/rbutilqt/lang/rbutil_nl.ts b/utils/rbutilqt/lang/rbutil_nl.ts index e5312ce2a2..e9e8c01887 100644 --- a/utils/rbutilqt/lang/rbutil_nl.ts +++ b/utils/rbutilqt/lang/rbutil_nl.ts @@ -668,8 +668,8 @@ Zie https://www.rockbox.org/wiki/IpodConversionToFAT32 - Could not open Ipod in R/W mode - Kon Ipod niet openen in R/W modus + Could not open iPod in R/W mode + Kon iPod niet openen in R/W modus @@ -718,18 +718,18 @@ Zie https://www.rockbox.org/wiki/IpodConversionToFAT32 - Could not open Ipod: permission denied - Kon Ipod niet openen: toestemming geweigerd + Could not open iPod: permission denied + Kon iPod niet openen: toestemming geweigerd - Could not open Ipod - Kon Ipod niet openen + Could not open iPod + Kon iPod niet openen - Error reading partition table - possibly not an Ipod - Fout tijdens lezen van de partitie tabel - mogelijks geen Ipod + Error reading partition table - possibly not an iPod + Fout tijdens lezen van de partitie tabel - mogelijks geen iPod @@ -1254,9 +1254,9 @@ Rockbox Utility kan niet werken met deze instellingen. Stel de systeemproxy-inst %1 "MacPod" found! -Rockbox needs a FAT formatted Ipod (so-called "WinPod") to run. +Rockbox needs a FAT formatted iPod (so-called "WinPod") to run. %1 "MacPod" gevonden! -Rockbox heeft een FAT-geformattered Ipod (een zogenaamde "WinPod") nodig om te kunnen draaien. +Rockbox heeft een FAT-geformattered iPod (een zogenaamde "WinPod") nodig om te kunnen draaien. diff --git a/utils/rbutilqt/lang/rbutil_pl.ts b/utils/rbutilqt/lang/rbutil_pl.ts index be09ef5614..ec9805e585 100644 --- a/utils/rbutilqt/lang/rbutil_pl.ts +++ b/utils/rbutilqt/lang/rbutil_pl.ts @@ -673,7 +673,7 @@ Zobacz https://www.rockbox.org/wiki/IpodConversionToFAT32 - Could not open Ipod in R/W mode + Could not open iPod in R/W mode Nie można otworzyć iPoda w trybie odczytu/zapisu @@ -728,17 +728,17 @@ Zobacz https://www.rockbox.org/wiki/IpodConversionToFAT32 - Could not open Ipod: permission denied + Could not open iPod: permission denied Nie można otworzyć iPoda: dostęp zabroniony - Could not open Ipod + Could not open iPod Nie można otworzyć iPoda - Error reading partition table - possibly not an Ipod + Error reading partition table - possibly not an iPod Błąd odczytu tablicy partycji - możliwe że urządzenie nie jest iPodem @@ -1366,9 +1366,9 @@ Rockbox Utility nie może pracować z tymi ustawieniami. Proszę sprawdź, że u %1 "MacPod" found! -Rockbox needs a FAT formatted Ipod (so-called "WinPod") to run. +Rockbox needs a FAT formatted iPod (so-called "WinPod") to run. znaleziono \"MaxPod\" %1! -Rockbox obsługuje wyłącznie Ipody z systemem plików FAT (tak zwane \"WinPod\"). +Rockbox obsługuje wyłącznie iPody z systemem plików FAT (tak zwane \"WinPod\"). Fatal error diff --git a/utils/rbutilqt/lang/rbutil_pt.ts b/utils/rbutilqt/lang/rbutil_pt.ts index 44d652d6ac..db51435926 100644 --- a/utils/rbutilqt/lang/rbutil_pt.ts +++ b/utils/rbutilqt/lang/rbutil_pt.ts @@ -659,8 +659,8 @@ See https://www.rockbox.org/wiki/IpodConversionToFAT32 - Could not open Ipod in R/W mode - Falha na abertura do Ipod em modo de leitura e escrita (R/W) + Could not open iPod in R/W mode + Falha na abertura do iPod em modo de leitura e escrita (R/W) @@ -709,13 +709,13 @@ See https://www.rockbox.org/wiki/IpodConversionToFAT32 - Could not open Ipod: permission denied + Could not open iPod: permission denied - Could not open Ipod - Não consegiu abrir o Ipod + Could not open iPod + Não consegiu abrir o iPod @@ -734,7 +734,7 @@ See https://www.rockbox.org/wiki/IpodConversionToFAT32 - Error reading partition table - possibly not an Ipod + Error reading partition table - possibly not an iPod @@ -1311,7 +1311,7 @@ Rockbox Utility can't work with this proxy settings. Make sure the system p %1 "MacPod" found! -Rockbox needs a FAT formatted Ipod (so-called "WinPod") to run. +Rockbox needs a FAT formatted iPod (so-called "WinPod") to run. diff --git a/utils/rbutilqt/lang/rbutil_pt_BR.ts b/utils/rbutilqt/lang/rbutil_pt_BR.ts index 11798b877e..d4c5c82222 100644 --- a/utils/rbutilqt/lang/rbutil_pt_BR.ts +++ b/utils/rbutilqt/lang/rbutil_pt_BR.ts @@ -658,8 +658,8 @@ - Could not open Ipod in R/W mode - Não foi possível abrir o Ipod em modo R/W (Leitura/Escrita) + Could not open iPod in R/W mode + Não foi possível abrir o iPod em modo R/W (Leitura/Escrita) @@ -693,13 +693,13 @@ - Could not open Ipod - Não foi possível abrir o Ipod + Could not open iPod + Não foi possível abrir o iPod - Error reading partition table - possibly not an Ipod - Erro lendo tabela de partição - possivelmente não é um Ipod + Error reading partition table - possibly not an iPod + Erro lendo tabela de partição - possivelmente não é um iPod @@ -730,8 +730,8 @@ Veja https://www.rockbox.org/wiki/IpodConversionToFAT32 - Could not open Ipod: permission denied - Não foi possível abrir o Ipod: permissão negada + Could not open iPod: permission denied + Não foi possível abrir o iPod: permissão negada @@ -1399,9 +1399,9 @@ Por favor, configure o motor TTS. %1 "MacPod" found! -Rockbox needs a FAT formatted Ipod (so-called "WinPod") to run. +Rockbox needs a FAT formatted iPod (so-called "WinPod") to run. %1 encontrado "MacPod"! -O Rockbox precisa de um Ipod formatado como FAT (chamado de "WinPod") para funcionar. +O Rockbox precisa de um iPod formatado como FAT (chamado de "WinPod") para funcionar. diff --git a/utils/rbutilqt/lang/rbutil_ru.ts b/utils/rbutilqt/lang/rbutil_ru.ts index aec30a697f..53b79a7561 100644 --- a/utils/rbutilqt/lang/rbutil_ru.ts +++ b/utils/rbutilqt/lang/rbutil_ru.ts @@ -661,7 +661,7 @@ - Could not open Ipod in R/W mode + Could not open iPod in R/W mode Не удалось открыть iPod в режиме записи @@ -686,7 +686,7 @@ - Could not open Ipod + Could not open iPod Не удалось открыть iPod @@ -706,7 +706,7 @@ - Error reading partition table - possibly not an Ipod + Error reading partition table - possibly not an iPod Сбой чтения таблицы разделов - возможно, это не iPod @@ -738,7 +738,7 @@ See https://www.rockbox.org/wiki/IpodConversionToFAT32 - Could not open Ipod: permission denied + Could not open iPod: permission denied Не удалось открыть iPod: доступ запрещён @@ -1402,7 +1402,7 @@ Please configure TTS engine. %1 "MacPod" found! -Rockbox needs a FAT formatted Ipod (so-called "WinPod") to run. +Rockbox needs a FAT formatted iPod (so-called "WinPod") to run. %1 является MacPod'ом! Для работы, Rockbox нужен iPod форматированный в FAT (так называемый "WinPod"). diff --git a/utils/rbutilqt/lang/rbutil_tr.ts b/utils/rbutilqt/lang/rbutil_tr.ts index 677e82b6e9..f9181ff8c9 100644 --- a/utils/rbutilqt/lang/rbutil_tr.ts +++ b/utils/rbutilqt/lang/rbutil_tr.ts @@ -660,7 +660,7 @@ See https://www.rockbox.org/wiki/IpodConversionToFAT32 - Could not open Ipod in R/W mode + Could not open iPod in R/W mode @@ -715,17 +715,17 @@ See https://www.rockbox.org/wiki/IpodConversionToFAT32 - Could not open Ipod: permission denied + Could not open iPod: permission denied - Could not open Ipod + Could not open iPod - Error reading partition table - possibly not an Ipod + Error reading partition table - possibly not an iPod @@ -1293,19 +1293,19 @@ See https://www.rockbox.org/wiki/SansaE200Install Orijinal bellenim (firmware) mevcut değil: %1 - Searching for ipods + Searching for iPods iPod'lar aranıyor - No Ipods found + No iPods found Herhangi bir iPod bulunamadı - Too many Ipods found + Too many iPods found Çok sayıda iPod bulundu - could not open ipod + could not open iPod iPod açılamadı @@ -1341,7 +1341,7 @@ See https://www.rockbox.org/wiki/IpodConversionToFAT32 Ayrıntılı bilgi için: https://www.rockbox.org/wiki/IpodConversionToFAT32 - Could not open Ipod in RW mode + Could not open iPod in RW mode iPod RW modunda açılamadı @@ -1711,7 +1711,7 @@ You need to change your player to MSC mode for installation. %1 "MacPod" found! -Rockbox needs a FAT formatted Ipod (so-called "WinPod") to run. +Rockbox needs a FAT formatted iPod (so-called "WinPod") to run. diff --git a/utils/rbutilqt/lang/rbutil_zh_CN.ts b/utils/rbutilqt/lang/rbutil_zh_CN.ts index 38746fa6c8..4de2f875be 100644 --- a/utils/rbutilqt/lang/rbutil_zh_CN.ts +++ b/utils/rbutilqt/lang/rbutil_zh_CN.ts @@ -663,7 +663,7 @@ See https://www.rockbox.org/wiki/IpodConversionToFAT32 - Could not open Ipod in R/W mode + Could not open iPod in R/W mode 无法打开iPod读写模式 @@ -713,12 +713,12 @@ See https://www.rockbox.org/wiki/IpodConversionToFAT32 - Could not open Ipod: permission denied + Could not open iPod: permission denied 无法打开iPod:拒绝访问 - Could not open Ipod + Could not open iPod 无法打开iPod @@ -745,7 +745,7 @@ See https://www.rockbox.org/wiki/IpodConversionToFAT32 - Error reading partition table - possibly not an Ipod + Error reading partition table - possibly not an iPod 读取分区表时出错 - 可能不是 iPod @@ -1268,7 +1268,7 @@ Rockbox Utility 无法使用此代理设置。请确保系统代理设置正确 %1 "MacPod" found! -Rockbox needs a FAT formatted Ipod (so-called "WinPod") to run. +Rockbox needs a FAT formatted iPod (so-called "WinPod") to run. %1 发现Mac格式的iPod!! Rockbox需要FAT格式的iPod才能运行。 diff --git a/utils/rbutilqt/lang/rbutil_zh_TW.ts b/utils/rbutilqt/lang/rbutil_zh_TW.ts index b1b7265a6f..ee9a1691ec 100644 --- a/utils/rbutilqt/lang/rbutil_zh_TW.ts +++ b/utils/rbutilqt/lang/rbutil_zh_TW.ts @@ -655,7 +655,7 @@ See https://www.rockbox.org/wiki/IpodConversionToFAT32 - Could not open Ipod in R/W mode + Could not open iPod in R/W mode @@ -705,12 +705,12 @@ See https://www.rockbox.org/wiki/IpodConversionToFAT32 - Could not open Ipod: permission denied + Could not open iPod: permission denied - Could not open Ipod + Could not open iPod @@ -730,7 +730,7 @@ See https://www.rockbox.org/wiki/IpodConversionToFAT32 - Error reading partition table - possibly not an Ipod + Error reading partition table - possibly not an iPod @@ -1223,7 +1223,7 @@ Rockbox Utility can't work with this proxy settings. Make sure the system p %1 "MacPod" found! -Rockbox needs a FAT formatted Ipod (so-called "WinPod") to run. +Rockbox needs a FAT formatted iPod (so-called "WinPod") to run. diff --git a/utils/rbutilqt/rbutil.ini b/utils/rbutilqt/rbutil.ini index f4711afd99..2611b047d5 100644 --- a/utils/rbutilqt/rbutil.ini +++ b/utils/rbutilqt/rbutil.ini @@ -241,7 +241,7 @@ playerpic=iriverh10 encoder=rbspeex [ipod1g2g] -name="Ipod (1st / 2nd gen)" +name="iPod (1st / 2nd gen)" bootloadermethod=ipod bootloadername=/ipod/bootloader-ipod1g2g.ipod manualname= @@ -251,7 +251,7 @@ playerpic=ipod1g2g encoder=rbspeex [ipodcolor] -name="Ipod Color / Photo / U2 (4th gen)" +name="iPod Color / Photo / U2 (4th gen)" bootloadermethod=ipod bootloadername=/ipod/bootloader-ipodcolor.ipod manualname= @@ -261,7 +261,7 @@ playerpic=ipodcolor encoder=rbspeex [ipodnano1g] -name="Ipod Nano (1st gen)" +name="iPod Nano (1st gen)" bootloadermethod=ipod bootloadername=/ipod/bootloader-ipodnano1g.ipod manualname= @@ -271,7 +271,7 @@ playerpic=ipodnano1g encoder=rbspeex [ipodnano2g] -name="Ipod Nano (2nd gen)" +name="iPod Nano (2nd gen)" bootloadermethod=ipod bootloadername=/ipod/bootloader-ipodnano2g.ipodx manualname= @@ -281,7 +281,7 @@ playerpic=ipodnano2g encoder=rbspeex [ipod4g] -name="Ipod (4th gen, greyscale)" +name="iPod (4th gen, greyscale)" bootloadermethod=ipod bootloadername=/ipod/bootloader-ipod4g.ipod manualname= @@ -291,7 +291,7 @@ playerpic=ipod4g encoder=rbspeex [ipodvideo] -name="Ipod Video (5th gen)" +name="iPod Video (5th gen)" bootloadermethod=ipod bootloadername=/ipod/bootloader-ipodvideo.ipod manualname= @@ -301,7 +301,7 @@ playerpic=ipodvideo encoder=rbspeex [ipod6g] -name="Ipod Classic (6th gen)" +name="iPod Classic (6th gen)" bootloadermethod=s5l bootloadername=/ipod/bootloader-ipod6g.ipod manualname= @@ -311,7 +311,7 @@ playerpic=ipod6g encoder=rbspeex [ipod3g] -name="Ipod (3rd gen)" +name="iPod (3rd gen)" bootloadermethod=ipod bootloadername=/ipod/bootloader-ipod3g.ipod manualname= @@ -321,7 +321,7 @@ playerpic=ipod3g encoder=rbspeex [ipodmini1g] -name="Ipod Mini (1st gen)" +name="iPod Mini (1st gen)" bootloadermethod=ipod bootloadername=/ipod/bootloader-ipodmini1g.ipod brand=Apple @@ -330,7 +330,7 @@ playerpic=ipodmini1g encoder=rbspeex [ipodmini2g] -name="Ipod Mini (2nd gen)" +name="iPod Mini (2nd gen)" bootloadermethod=ipod bootloadername=/ipod/bootloader-ipodmini2g.ipod manualname=ipodmini1g @@ -1015,25 +1015,25 @@ pathsuffix=/FunKey ; each section uses a USB VID / PID string as section name. ; name: human readable string to show the user when this device is detected. [05ac1240] -name="Apple Ipod Nano (Second Generation, DFU Mode)" +name="Apple iPod Nano (Second Generation, DFU Mode)" [05ac1262] -name="Apple Ipod Nano (Third Generation)" +name="Apple iPod Nano (Third Generation)" [05ac1263] -name="Apple Ipod Nano (Fourth Generation)" +name="Apple iPod Nano (Fourth Generation)" [05ac1255] -name="Apple Ipod Nano (Fourth Generation, DFU Mode)" +name="Apple iPod Nano (Fourth Generation, DFU Mode)" [05ac1265] -name="Apple Ipod Nano (Fifth Generation)" +name="Apple iPod Nano (Fifth Generation)" [05ac1266] -name="Apple Ipod Nano (Sixth Generation)" +name="Apple iPod Nano (Sixth Generation)" [05ac1267] -name="Apple Ipod Nano (Seventh Generation)" +name="Apple iPod Nano (Seventh Generation)" [041e4133] name="Creative Zen series (Removable Disk Mode)" diff --git a/utils/rk27utils/rk27load/stage1/Makefile b/utils/rk27utils/rk27load/stage1/Makefile index 5291685032..a783977533 100644 --- a/utils/rk27utils/rk27load/stage1/Makefile +++ b/utils/rk27utils/rk27load/stage1/Makefile @@ -10,7 +10,7 @@ AS = $(TOOLCHAIN)as OBJCOPY = $(TOOLCHAIN)objcopy OBJDUMP = $(TOOLCHAIN)objdump -CFLAGS = -Wundef -marm -march=armv5te -nostdlib -mfpu=fpa -O0 -c +CFLAGS = -Wundef -marm -march=armv5te -nostdlib -O0 -c #ASFLAGS = -mcpu=arm926ej-s OBJS = main.o @@ -20,7 +20,7 @@ LDSCRIPT= stage1.lds #LIBS = -lgcc LIBS = LDFLAGS = -Wundef -marm -march=armv5te -T$(LDSCRIPT) -nostartfiles \ - -mfpu=fpa -nostdlib -Xlinker -Map=$(TARGET).map + -nostdlib -Xlinker -Map=$(TARGET).map all : $(TARGET).bin ls -ls $(TARGET).bin diff --git a/utils/rk27utils/rk27load/stage2/Makefile b/utils/rk27utils/rk27load/stage2/Makefile index 4b216db2f3..37bfcccfdc 100644 --- a/utils/rk27utils/rk27load/stage2/Makefile +++ b/utils/rk27utils/rk27load/stage2/Makefile @@ -10,7 +10,7 @@ AS = $(TOOLCHAIN)as OBJCOPY = $(TOOLCHAIN)objcopy OBJDUMP = $(TOOLCHAIN)objdump -CFLAGS = -Wundef -marm -march=armv5te -nostdlib -mfpu=fpa -O0 -c +CFLAGS = -Wundef -marm -march=armv5te -nostdlib -O0 -c #ASFLAGS = -mcpu=arm926ej-s OBJS = crt0.o main.o irq.o @@ -20,7 +20,7 @@ LDSCRIPT= stage2.lds #LIBS = -lgcc LIBS = LDFLAGS = -Wundef -marm -march=armv5te -T$(LDSCRIPT) -nostartfiles \ - -mfpu=fpa -nostdlib -Xlinker -Map=$(TARGET).map + -nostdlib -Xlinker -Map=$(TARGET).map all : $(TARGET).bin ls -ls $(TARGET).bin diff --git a/utils/rockbox_api/index.tpl b/utils/rockbox_api/index.tpl index ca8f602fd2..4d30f87fbb 100644 --- a/utils/rockbox_api/index.tpl +++ b/utils/rockbox_api/index.tpl @@ -1,9 +1,9 @@ - + - - + + Rockbox Plugin API - INDEX @@ -13,47 +13,47 @@
-
- Rockbox.org home +
- releases
- current build
- extras + releases
+ current build
+ extras - FAQ
- manual
- wiki
- docs index + FAQ
+ manual
+ wiki
+ docs index - mailing lists
+ mailing lists
- IRC
- forums + IRC
+ forums - bugs
- patches
+ bugs
+ patches
-
+

-
+
diff --git a/utils/rockbox_api/section.tpl b/utils/rockbox_api/section.tpl index 6fb019538b..c2bd7365b0 100644 --- a/utils/rockbox_api/section.tpl +++ b/utils/rockbox_api/section.tpl @@ -1,9 +1,9 @@ - + - - + + Rockbox Plugin API - %GROUP_NAME% @@ -14,46 +14,46 @@
- releases
- current build
- extras + releases
+ current build
+ extras - FAQ
- manual
- wiki
- docs index + FAQ
+ manual
+ wiki
+ docs index - mailing lists
+ mailing lists
- IRC
- forums + IRC
+ forums - bugs
- patches
+ bugs
+ patches
- +

-
+
diff --git a/utils/themeeditor/CMakeLists.txt b/utils/themeeditor/CMakeLists.txt index 8720ada7c3..8493793d71 100644 --- a/utils/themeeditor/CMakeLists.txt +++ b/utils/themeeditor/CMakeLists.txt @@ -120,6 +120,14 @@ target_link_libraries(RockboxThemeEditor target_include_directories(RockboxThemeEditor PRIVATE models graphics gui qtfindreplacedialog quazip) target_compile_definitions(RockboxThemeEditor PRIVATE FINDREPLACE_NOLIB) +# This is an issue with the code produced by QT's MOC +if (CMAKE_CXX_COMPILER_ID MATCHES "GNU") + check_cxx_compiler_flag(-Wsfinae-incomplete=0 HAVE_SFINAE_INCOMPLETE) + if (HAVE_SFINAE_INCOMPLETE) + target_compile_options(RockboxThemeEditor PRIVATE -Wsfinae-incomplete=0) + endif() +endif() + if(APPLE) # MacOS Application Bundle specifics. target_sources(RockboxThemeEditor PRIVATE diff --git a/utils/themeeditor/RockboxThemeEditor.desktop b/utils/themeeditor/RockboxThemeEditor.desktop index a8a731fcf9..818bc60947 100644 --- a/utils/themeeditor/RockboxThemeEditor.desktop +++ b/utils/themeeditor/RockboxThemeEditor.desktop @@ -1,6 +1,5 @@ [Desktop Entry] GenericName=Rockbox Theme Editor -Version=1.0 Hidden=false Name=Rockbox Theme Editor Type=Application diff --git a/utils/tools/bin2c.c b/utils/tools/bin2c.c index 4600746d52..27101fff6e 100644 --- a/utils/tools/bin2c.c +++ b/utils/tools/bin2c.c @@ -42,7 +42,7 @@ static void usage(void) { fprintf(stderr, "bin2c [options] infile cfile\n"); - fprintf(stderr, " -i ipod mode\n"); + fprintf(stderr, " -i iPod mode\n"); } diff --git a/wps/WPSLIST b/wps/WPSLIST index 3e0ba230e0..5e303ebc04 100644 --- a/wps/WPSLIST +++ b/wps/WPSLIST @@ -85,6 +85,7 @@ wps.480x720x(16|24|32): cabbiev2.480x720x16.touch.wps wps.800x480x(16|24|32): cabbiev2.800x480x16.wps wps.480x800x(16|24|32): cabbiev2.480x800x16.wps wps.400x240x(16|24|32): cabbiev2.400x240x16.wps +wps.360x640x(16|24|32): cabbiev2.360x640x16.wps wps.360x400x(16|24|32): cabbiev2.360x400x16.wps wps.320x480x(16|24|32): cabbiev2.320x480x16.wps wps.320x240x(16|24|32): cabbiev2.320x240x16.wps @@ -109,6 +110,7 @@ wps.112x64x1: cabbiev2.112x64x1.wps wps.96x96x(16|24|32): cabbiev2.96x96x16.wps # override implicit .fms filename +fms.400x240x(16|24|32): cabbiev2-400x240x16.fms fms.160x128x2: cabbiev2-160x128x2.fms fms.128x128x2: cabbiev2-128x128x2.fms @@ -118,6 +120,7 @@ Font.480x720x(16|24|32): 35-Adobe-Helvetica.fnt Font.800x480x(16|24|32): 35-Adobe-Helvetica.fnt Font.480x800x(16|24|32): 35-Adobe-Helvetica.fnt Font.400x240x(16|24|32): 15-Adobe-Helvetica.fnt +Font.360x640x(16|24|32): 35-Adobe-Helvetica.fnt Font.360x400x(16|24|32): 18-Adobe-Helvetica.fnt Font.320x480x(16|24|32): 27-Adobe-Helvetica.fnt Font.320x240x(16|24|32): 15-Adobe-Helvetica.fnt @@ -155,6 +158,7 @@ backdrop.480x720x(16|24|32): backdrops/cabbiev2.480x720x16.bmp backdrop.800x480x(16|24|32): backdrops/cabbiev2.800x480x16.bmp backdrop.480x800x(16|24|32): backdrops/cabbiev2.480x800x16.bmp backdrop.400x240x(16|24|32): backdrops/cabbiev2.400x240x16.bmp +backdrop.360x640x(16|24|32): backdrops/cabbiev2.360x640x16.bmp backdrop.360x400x(16|24|32): backdrops/cabbiev2.360x400x16.bmp backdrop.320x480x(16|24|32): backdrops/cabbiev2.320x480x16.bmp backdrop.320x240x(16|24|32): backdrops/cabbiev2.320x240x16.bmp @@ -185,6 +189,7 @@ iconset.480x720x(16|24|32): icons/tango_icons.32x32.bmp iconset.800x480x(16|24|32): icons/tango_icons.32x32.bmp iconset.480x800x(16|24|32): icons/tango_icons.32x32.bmp iconset.400x240x(16|24|32): icons/tango_icons.16x16.bmp +iconset.360x640x(16|24|32): icons/tango_icons.32x32.bmp iconset.360x400x(16|24|32): icons/tango_icons.32x32.bmp iconset.320x480x(16|24|32): icons/tango_icons.24x24.bmp iconset.320x240x(16|24|32): icons/tango_icons.16x16.bmp @@ -208,6 +213,7 @@ viewers iconset.480x720x(16|24|32): icons/tango_icons_viewers.32x32.bmp viewers iconset.800x480x(16|24|32): icons/tango_icons_viewers.32x32.bmp viewers iconset.480x800x(16|24|32): icons/tango_icons_viewers.32x32.bmp viewers iconset.400x240x(16|24|32): icons/tango_icons_viewers.16x16.bmp +viewers iconset.360x640x(16|24|32): icons/tango_icons_viewers.32x32.bmp viewers iconset.360x400x(16|24|32): icons/tango_icons_viewers.32x32.bmp viewers iconset.320x480x(16|24|32): icons/tango_icons_viewers.24x24.bmp viewers iconset.320x240x(16|24|32): icons/tango_icons_viewers.16x16.bmp diff --git a/wps/cabbiev2-400x240x16.fms b/wps/cabbiev2-400x240x16.fms new file mode 100644 index 0000000000..9e5199efca --- /dev/null +++ b/wps/cabbiev2-400x240x16.fms @@ -0,0 +1,94 @@ +# Cabbie v2.0 +# (C) 2010-2026 The Authors (see /rockbox/wps/AUTHORS) +# Derived from "Cabbie v2.0" (C) 2010-2012 The Authors +# +# One backdrop for the WPS and the FM screen: the header label is drawn, +# rather than painted into it. +# +# Disable Status Bar +%wd +# +# Load Backdrop +%X(backdrop-400x240x16.bmp) +# +# Header font +%Fl(2,12-Adobe-Helvetica-Bold.fnt) +# +# Preload Images +%xl(lock,lock-320x240x16.bmp,0,0,2) +%xl(battery,battery-320x240x16.bmp,0,0,10) +%xl(volume,volume-320x240x16.bmp,0,0,10) +# +# Station Art/Info Viewport Conditional +# Preset present conditional +# RDS present conditional +%?C<%Vd(art)> +%?Ti<%?C<%Vd(art_preset)|%Vd(noart_preset)>> +%?tx<%?Ti<%?C<%Vd(art_preset_rds)|%Vd(noart_preset_rds)>|%?C<%Vd(art_nopreset_rds)|%Vd(noart_nopreset_rds)>>|> +# +# Header +%V(10,11,380,12,2)%Vf(0F0B00) +%acFM RADIO +# +# Frequency Bar +%V(10,162,380,15,-) +%pb(0,0,380,15,pb-400x240x16.bmp) +# +# Hold +%V(144,207,30,23,-) +%xd(lock,%mh) +# +# Battery +%V(182,207,44,23,-) +%?bp<%?bc<%xd(battery,1)|%xd(battery,2)>|%xd(battery,%bl,2)> +# +# Volume +%V(233,207,34,23,-) +%xd(volume,%pv) +# +# Signal strength +%V(277,207,113,23,1) +%ar%tr dBuV +# +# Below frequency bar Scan/Preset frequency MHz Force Mono/Mono/Stereo +%V(32,180,336,20,1) +%al%?tm<%Sx(Scan)|%Sx(Preset)> %ac%tf MHz %ar%?St(force fm mono)<%Sx(Force Mono)|%?ts<%Sx(Stereo)|%Sx(Mono)>> +# +# Station Art +%ax%Vl(art,16,32,120,120,-) +%Cl(0,0,120,120,c,c) +%Cd +# +# Station Art +# Preset present +%ax%Vl(art_preset,153,40,-,20,1) +%Vp(0,%s%al%?Ti%?Tn<%Tn|%Tf>) +%ax%Vl(art_preset,153,62,-,20,1) +%Vp(1,%s%al%?Ti%?Tn<%Tn|%Tf>) +# +%ax%Vl(art_preset_rds,153,94,-,40,1) +%s%al%ty +%s%al%tz +# +# Station Art +# Preset absent +%ax%Vl(art_nopreset_rds,153,62,-,40,1) +%s%al%ty +%s%al%tz +# +# No station art +# Preset present +%Vl(noart_preset,10,40,380,20,1) +%Vp(0,%s%ac%?Ti%?Tn<%Tn|%Tf>) +%Vl(noart_preset,10,62,380,20,1) +%Vp(1,%s%ac%?Ti%?Tn<%Tn|%Tf>) +# +%Vl(noart_preset_rds,10,94,380,40,1) +%s%ac%ty +%s%ac%tz +# +# No station art +# Preset absent +%Vl(noart_nopreset_rds,10,62,380,40,1) +%s%ac%ty +%s%ac%tz diff --git a/wps/cabbiev2.360x640x16.wps b/wps/cabbiev2.360x640x16.wps new file mode 100644 index 0000000000..b44ec434a3 --- /dev/null +++ b/wps/cabbiev2.360x640x16.wps @@ -0,0 +1,87 @@ +# Cabbie v2.0 +# (C) 2007-2012 The Authors (see /rockbox/wps/AUTHORS) +# Derived from "cabbie" (C) Yohann Misquitta +# +# Laid out against wpsbackdrop-360x640x16.bmp, whose fixed landmarks are: +# title bar x=30..329 y=6..27 +# progress trough x=30..329 y=516..537 +# status row y=605..633, with shuffle baked at x=209 and repeat at x=273 +# Everything below is anchored to those; the content area is y=28..515. +# +# Disable Status Bar +%wd +# +# Load Backdrop +%X(wpsbackdrop-360x640x16.bmp) +# +# Preload Images +%xl(A,lock-360x640x16.bmp,0,0,2) +%xl(B,battery-360x640x16.bmp,0,0,10) +%xl(C,volume-360x640x16.bmp,0,0,10) +%xl(D,shuffle-360x640x16.bmp,0,0) +%xl(E,repeat-360x640x16.bmp,0,0,4) +%xl(F,playmode-360x640x16.bmp,0,0,5) +# +# Album Art/Info Viewport Conditional +%?C<%Vd(a)|%Vd(b)> +# +# Progress Bar +# Backdrop trough occupies x=30..329, y=516..537. +%V(30,516,300,20,-) +%pb(0,0,-,20,pb-360x640x16.bmp) +# +# Hold +%V(16,605,38,29,-) +%?mh<%xd(Aa)|%xd(Ab)> +# +# Battery +%V(69,605,66,29,-) +%?bp<%?bc<%xd(Ba)|%xd(Bb)>|%?bl<|%xd(Bc)|%xd(Bd)|%xd(Be)|%xd(Bf)|%xd(Bg)|%xd(Bh)|%xd(Bi)|%xd(Bj)>> +# +# Volume +%V(147,605,50,29,-) +%?pv<%xd(Ca)|%xd(Cb)|%xd(Cc)|%xd(Cd)|%xd(Ce)|%xd(Cf)|%xd(Cg)|%xd(Ch)|%xd(Ci)|%xd(Cj)> +# +# Shuffle +%V(208,605,55,29,-) +%?ps<%xd(D)> +# +# Repeat +%V(273,605,27,29,-) +%?mm<|%xd(Ea)|%xd(Eb)|%xd(Ec)|%xd(Ed)> +# +# Playmode +%V(309,605,36,29,-) +%?mp<%xd(Fa)|%xd(Fb)|%xd(Fc)|%xd(Fd)|%xd(Fe)> +# +# Time Elapsed/Remaining +%V(30,542,300,36,-) +%al%pc%ac%?Sr<%pe %Sx(of) %pp|%pp %Sx(of) %pe>%ar%pr +# +# Album Art +%ax%Vl(a,40,45,280,280,-) +%Cl(0,0,280,280,c,c) +%Cd +# +# Track Info - Album Art +%ax%Vl(a,0,329,-,187,1) +%s%ac%?id<%id|%?d(1)<%d(1)|%(root%)>> +%s%ac%?it<%it|%fn> +%s%ac%?ia<%ia|%?iA<%iA|%?d(2)<%d(2)|%(root%)>>> + +%s%ac%Sx(Next:) %?Ia<%Ia|%?IA<%IA|%?D(2)<%D(2)|%(root%)>>> - %?It<%It|%Fn> +# +# Track Info - No Album Art +%Vl(b,0,96,-,420,1) +%s%ac%?id<%id|%?d(1)<%d(1)|%(root%)>> +%s%ac%?it<%it|%fn> +%s%ac%?ia<%ia|%?iA<%iA|%?d(2)<%d(2)|%(root%)>>> +%ac%?iy<%iy|> + +%ac%?ig<%ig|> +%ac%?fv<%(vbr%) |>%fb kbit/s %fc + +%ac%Sx(Next:) +%s%ac%?Id<%Id|%?D(1)<%D(1)|%(root%)>> +%ac%s%?It<%It|%Fn> +%s%ac%?Ia<%Ia|%?IA<%IA|%?D(2)<%D(2)|%(root%)>>> diff --git a/wps/cabbiev2.400x240x16.wps b/wps/cabbiev2.400x240x16.wps index 469ee3d2f1..e9294d32a6 100644 --- a/wps/cabbiev2.400x240x16.wps +++ b/wps/cabbiev2.400x240x16.wps @@ -1,12 +1,18 @@ # Cabbie v2.0 -# (C) 2007-2012 The Authors (see /rockbox/wps/AUTHORS) +# (C) 2007-2026 The Authors (see /rockbox/wps/AUTHORS) # Derived from "cabbie" (C) Yohann Misquitta # +# One backdrop for the WPS and the FM screen: the header label is drawn, +# rather than painted into it. +# # Disable Status Bar %wd # # Load Backdrop -%X(wpsbackdrop-400x240x16.bmp) +%X(backdrop-400x240x16.bmp) +# +# Header font +%Fl(2,12-Adobe-Helvetica-Bold.fnt) # # Preload Images %xl(A,lock-320x240x16.bmp,0,0,2) @@ -15,10 +21,16 @@ %xl(D,shuffle-320x240x16.bmp,0,0) %xl(E,repeat-320x240x16.bmp,0,0,4) %xl(F,playmode-320x240x16.bmp,0,0,5) +%xl(G,shuffleoff-400x240x16.bmp,0,0) +%xl(H,repeatoff-400x240x16.bmp,0,0) # # Album Art/Info Viewport Conditional %?C<%Vd(a)|%Vd(b)> # +# Header +%V(10,11,380,12,2)%Vf(0F0B00) +%acNOW PLAYING +# # Progress Bar %V(10,162,380,15,-) %pb(0,0,380,15,pb-400x240x16.bmp) @@ -37,11 +49,11 @@ # # Shuffle %V(277,211,37,16,-) -%?ps<%xd(D)> +%?ps<%xd(D)|%xd(G)> # # Repeat %V(320,207,18,23,-) -%?mm<|%xd(Ea)|%xd(Eb)|%xd(Ec)|%xd(Ed)> +%?mm<%xd(H)|%xd(Ea)|%xd(Eb)|%xd(Ec)|%xd(Ed)> # # Playmode %V(346,207,24,23,-) diff --git a/wps/cabbiev2/wpsbackdrop-400x240x16.bmp b/wps/cabbiev2/backdrop-400x240x16.bmp similarity index 88% rename from wps/cabbiev2/wpsbackdrop-400x240x16.bmp rename to wps/cabbiev2/backdrop-400x240x16.bmp index 3a7514f4e1..3f04ea6caa 100644 Binary files a/wps/cabbiev2/wpsbackdrop-400x240x16.bmp and b/wps/cabbiev2/backdrop-400x240x16.bmp differ diff --git a/wps/cabbiev2/battery-360x640x16.bmp b/wps/cabbiev2/battery-360x640x16.bmp new file mode 100644 index 0000000000..d00b4c1c5d Binary files /dev/null and b/wps/cabbiev2/battery-360x640x16.bmp differ diff --git a/wps/cabbiev2/lock-360x640x16.bmp b/wps/cabbiev2/lock-360x640x16.bmp new file mode 100644 index 0000000000..70c419e875 Binary files /dev/null and b/wps/cabbiev2/lock-360x640x16.bmp differ diff --git a/wps/cabbiev2/pb-360x640x16.bmp b/wps/cabbiev2/pb-360x640x16.bmp new file mode 100644 index 0000000000..6b5884d059 Binary files /dev/null and b/wps/cabbiev2/pb-360x640x16.bmp differ diff --git a/wps/cabbiev2/playmode-360x640x16.bmp b/wps/cabbiev2/playmode-360x640x16.bmp new file mode 100644 index 0000000000..148376b127 Binary files /dev/null and b/wps/cabbiev2/playmode-360x640x16.bmp differ diff --git a/wps/cabbiev2/repeat-360x640x16.bmp b/wps/cabbiev2/repeat-360x640x16.bmp new file mode 100644 index 0000000000..f6fb08d909 Binary files /dev/null and b/wps/cabbiev2/repeat-360x640x16.bmp differ diff --git a/wps/cabbiev2/repeatoff-400x240x16.bmp b/wps/cabbiev2/repeatoff-400x240x16.bmp new file mode 100644 index 0000000000..675a917f21 Binary files /dev/null and b/wps/cabbiev2/repeatoff-400x240x16.bmp differ diff --git a/wps/cabbiev2/shuffle-360x640x16.bmp b/wps/cabbiev2/shuffle-360x640x16.bmp new file mode 100644 index 0000000000..1b16153176 Binary files /dev/null and b/wps/cabbiev2/shuffle-360x640x16.bmp differ diff --git a/wps/cabbiev2/shuffleoff-400x240x16.bmp b/wps/cabbiev2/shuffleoff-400x240x16.bmp new file mode 100644 index 0000000000..1a0f325ed9 Binary files /dev/null and b/wps/cabbiev2/shuffleoff-400x240x16.bmp differ diff --git a/wps/cabbiev2/volume-360x640x16.bmp b/wps/cabbiev2/volume-360x640x16.bmp new file mode 100644 index 0000000000..7d8b8e40e8 Binary files /dev/null and b/wps/cabbiev2/volume-360x640x16.bmp differ diff --git a/wps/cabbiev2/wpsbackdrop-360x640x16.bmp b/wps/cabbiev2/wpsbackdrop-360x640x16.bmp new file mode 100644 index 0000000000..a7ad1c6447 Binary files /dev/null and b/wps/cabbiev2/wpsbackdrop-360x640x16.bmp differ diff --git a/wps/wpsbuild.pl b/wps/wpsbuild.pl index e3e8c178ec..7491224039 100755 --- a/wps/wpsbuild.pl +++ b/wps/wpsbuild.pl @@ -231,6 +231,13 @@ sub copywps open(SKIN, "$dir/$file"); while () { $filelist[$#filelist + 1] = $1 if (/[\(,]([^,]*?.bmp)[\),]/); + # fonts the skin loads itself ship with it, as the theme + # font does: a default theme must not need the font pack. + # A copy of $1: copythemefont()'s own matching resets it. + if (/%Fl\(\d+,([^,\)]+\.fnt)[,\)]/) { + my $skinfont = $1; + copythemefont($skinfont); + } } close(SKIN); } @@ -268,7 +275,7 @@ MOO "rwps" => $rwps, "rsbs" => $rsbs, "rfms" => $rfms); - for my $skin (keys %skinfiles) { + for my $skin (sort keys %skinfiles) { my $val = $skinfiles{$skin}; if (!defined($val)) { # dont put value if not defined (e.g. rwps when there's no remote) @@ -283,10 +290,13 @@ MOO } push @out, "selector type: $selecttype\n" if (defined($selecttype)); - push @out, "backdrop: $backdrop\n" if (defined($backdrop)); - push @out, "filetype colours: $filetylecolor\n" if (defined($filetylecolor)); + + if ($main_depth > 1) { + push @out, "backdrop: $backdrop\n" if (defined($backdrop)); + } if ($main_depth > 2) { + push @out, "filetype colours: $filetylecolor\n" if (defined($filetylecolor)); push @out, "foreground color: $fgcolor\n" if($fgcolor); push @out, "background color: $bgcolor\n" if($bgcolor); push @out, "line selector start color: $lineselectstart\n" if($lineselectstart); @@ -400,7 +410,7 @@ $wpsdir = $1; open(WPS, "<$wpslist"); while() { my $l = $_; - + # remove CR $l =~ s/\r//g; if($l =~ /^ *\#/) {