/*
* This file is part of libbluray
* Copyright (C) 2010-2017 Petri Hintukainen <phintuka@users.sourceforge.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* <http://www.gnu.org/licenses/>.
*/
#if HAVE_CONFIG_H
#include "config.h"
#endif
#include "hdmv_vm.h"
#include "mobj_data.h"
#include "hdmv_insn.h"
#include "mobj_parse.h"
#include "mobj_print.h"
#include "../register.h"
#include "util/macro.h"
#include "util/logging.h"
#include "util/mutex.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
typedef struct {
time_t time;
uint32_t mobj_id;
} NV_TIMER;
struct hdmv_vm_s {
BD_MUTEX mutex;
/* state */
uint32_t pc; /* program counter */
BD_REGISTERS *regs; /* player registers */
const MOBJ_OBJECT *object; /* currently running object code */
HDMV_EVENT event[5]; /* pending events to return */
NV_TIMER nv_timer; /* navigation timer */
uint64_t rand; /* RAND state */
/* movie objects */
MOBJ_OBJECTS *movie_objects; /* disc movie objects */
MOBJ_OBJECT *ig_object; /* current object from IG stream */
/* object currently playing playlist */
const MOBJ_OBJECT *playing_object;
uint32_t playing_pc;
/* suspended object */
const MOBJ_OBJECT *suspended_object;
uint32_t suspended_pc;
/* Available titles. Used to validate CALL_TITLE/JUMP_TITLE. */
uint8_t have_top_menu;
uint8_t have_first_play;
uint16_t num_titles;
};
/*
* save / restore VM state
*/
static int _save_state(HDMV_VM *p, uint32_t *s)
{
memset(s, 0, sizeof(*s) * HDMV_STATE_SIZE);
if (p->ig_object) {
BD_DEBUG(DBG_HDMV | DBG_CRIT, "_save_state() failed: button object running\n");
return -1;
}
if (p->object) {
BD_DEBUG(DBG_HDMV | DBG_CRIT, "_save_state() failed: movie object running\n");
return -1;
}
if (p->event[0].event != HDMV_EVENT_NONE) {
BD_DEBUG(DBG_HDMV | DBG_CRIT, "_save_state() failed: unprocessed events\n");
return -1;
}
if (p->playing_object) {
s[0] = (uint32_t)(p->playing_object - p->movie_objects->objects);
s[1] = p->playing_pc;
} else {
s[0] = (uint32_t)-1;
}
if (p->suspended_object) {
s[2] = (uint32_t)(p->suspended_object - p->movie_objects->objects);
s[3] = p->suspended_pc;
} else {
s[2] = (uint32_t)-1;
}
/* nv timer ? */
return 0;
}
static int _restore_state(HDMV_VM *p, const uint32_t *s)
{
if (s[0] == (uint32_t)-1) {
p->playing_object = NULL;
} else if (s[0] >= p->movie_objects->num_objects) {
BD_DEBUG(DBG_HDMV | DBG_CRIT, "_restore_state() failed: invalid playing object index\n");
return -1;
} else {
p->playing_object = &p->movie_objects->objects[s[0]];
}
p->playing_pc = s[1];
if (s[2] == (uint32_t)-1) {
p->suspended_object = NULL;
} else if (s[2] >= p->movie_objects->num_objects) {
BD_DEBUG(DBG_HDMV | DBG_CRIT, "_restore_state() failed: invalid suspended object index\n");
return -1;
} else {
p->suspended_object = &p->movie_objects->objects[s[2]];
}
p->suspended_pc = s[3];
p->object = NULL;
p->ig_object = NULL;
memset(p->event, 0, sizeof(p->event));
return 0;
}
int hdmv_vm_save_state(HDMV_VM *p, uint32_t *s)
{
int result;
bd_mutex_lock(&p->mutex);
result = _save_state(p, s);
bd_mutex_unlock(&p->mutex);
return result;
}
void hdmv_vm_restore_state(HDMV_VM *p, const uint32_t *s)
{
bd_mutex_lock(&p->mutex);
_restore_state(p, s);
bd_mutex_unlock(&p->mutex);
}
/*
* registers: PSR and GPR access
*/
#define PSR_FLAG 0x80000000
static int _is_valid_reg(uint32_t reg)
{
if (reg & PSR_FLAG) {
if (reg & ~0x8000007f) {
return 0;
}
} else {
if (reg & ~0x00000fff) {
return 0;
}
}
return 1;
}
static int _store_reg(HDMV_VM *p, uint32_t reg, uint32_t val)
{
if (!_is_valid_reg(reg)) {
BD_DEBUG(DBG_HDMV, "_store_reg(): invalid register 0x%x\n", reg);
return -1;
}
if (reg & PSR_FLAG) {
BD_DEBUG(DBG_HDMV, "_store_reg(): storing to PSR is not allowed\n");
return -1;
} else {
return bd_gpr_write(p->regs, reg, val);
}
}
static uint32_t _read_reg(HDMV_VM *p, uint32_t reg)
{
if (!_is_valid_reg(reg)) {
BD_DEBUG(DBG_HDMV, "_read_reg(): invalid register 0x%x\n", reg);
return 0;
}
if (reg & PSR_FLAG) {
return bd_psr_read(p->regs, reg & 0x7f);
} else {
return bd_gpr_read(p->regs, reg);
}
}
static uint32_t _read_setstream_regs(HDMV_VM *p, uint32_t val)
{
uint32_t flags = val & 0xf000f000;
uint32_t reg0 = val & 0xfff;
uint32_t reg1 = (val >> 16) & 0xfff;
uint32_t val0 = bd_gpr_read(p->regs, reg0) & 0x0fff;
uint32_t val1 = bd_gpr_read(p->regs, reg1) & 0x0fff;
return flags | val0 | (val1 << 16);
}
static uint32_t _read_setbuttonpage_reg(HDMV_VM *p, uint32_t val)
{
uint32_t flags = val & 0xc0000000;
uint32_t reg0 = val & 0x00000fff;
uint32_t val0 = bd_gpr_read(p->regs, reg0) & 0x3fffffff;
return flags | val0;
}
static int _store_result(HDMV_VM *p, MOBJ_CMD *cmd, uint32_t src, uint32_t dst, uint32_t src0, uint32_t dst0)
{
int ret = 0;
/* store result to destination register(s) */
if (dst != dst0) {
if (cmd->insn.imm_op1) {
BD_DEBUG(DBG_HDMV|DBG_CRIT, "storing to imm !\n");
return -1;
}
ret = _store_reg(p, cmd->dst, dst);
}
if (src != src0) {
if (cmd->insn.imm_op1) {
BD_DEBUG(DBG_HDMV|DBG_CRIT, "storing to imm !\n");
return -1;
}
ret += _store_reg(p, cmd->src, src);
}
return ret;
}
static uint32_t _fetch_operand(HDMV_VM *p, int setstream, int setbuttonpage, int imm, uint32_t value)
{
if (imm) {
return value;
}
if (setstream) {
return _read_setstream_regs(p, value);
} else if (setbuttonpage) {
return _read_setbuttonpage_reg(p, value);
} else {
return _read_reg(p, value);
}
}
static void _fetch_operands(HDMV_VM *p, MOBJ_CMD *cmd, uint32_t *dst, uint32_t *src)
{
HDMV_INSN *insn = &cmd->insn;
int setstream = (insn->grp == INSN_GROUP_SET &&
insn->sub_grp == SET_SETSYSTEM &&
( insn->set_opt == INSN_SET_STREAM ||
insn->set_opt == INSN_SET_SEC_STREAM));
int setbuttonpage = (insn->grp == INSN_GROUP_SET &&
insn->sub_grp == SET_SETSYSTEM &&
insn->set_opt == INSN_SET_BUTTON_PAGE);
*dst = *src = 0;
if (insn->op_cnt > 0) {
*dst = _fetch_operand(p, setstream, setbuttonpage, insn->imm_op1, cmd->dst);
}
if (insn->op_cnt > 1) {
*src = _fetch_operand(p, setstream, setbuttonpage, insn->imm_op2, cmd->src);
}
}
/*
* event queue
*/
static int _get_event(HDMV_VM *p, HDMV_EVENT *ev)
{
if (p->event[0].event != HDMV_EVENT_NONE) {
*ev = p->event[0];
memmove(p->event, p->event + 1, sizeof(p->event) - sizeof(p->event[0]));
return 0;
}
ev->event = HDMV_EVENT_NONE;
return -1;
}
static int _queue_event(HDMV_VM *p, hdmv_event_e event, uint32_t param)
{
unsigned i;
for (i = 0; i < sizeof(p->event) / sizeof(p->event[0]) - 1; i++) {
if (p->event[i].event == HDMV_EVENT_NONE) {
p->event[i].event = event;
p->event[i].param = param;
return 0;
}
}
BD_DEBUG(DBG_HDMV|DBG_CRIT, "_queue_event(%d, %d): queue overflow !\n", event, param);
return -1;
}
/*
* vm init
*/
HDMV_VM *hdmv_vm_init(struct bd_disc *disc, BD_REGISTERS *regs,
unsigned num_titles, unsigned first_play_available, unsigned top_menu_available)
{
HDMV_VM *p = calloc(1, sizeof(HDMV_VM));
if (!p) {
BD_DEBUG(DBG_CRIT, "out of memory\n");
return NULL;
}
/* read movie objects */
p->movie_objects = mobj_get(disc);
if (!p->movie_objects) {
X_FREE(p);
return NULL;
}
p->regs = regs;
p->num_titles = num_titles;
p->have_top_menu = top_menu_available;
p->have_first_play = first_play_available;
#ifdef DEBUG
p->rand = 1;
#else
p->rand = time(NULL);
#endif
bd_mutex_init(&p->mutex);
return p;
}
static void _free_ig_object(HDMV_VM *p)
{
if (p->ig_object) {
X_FREE(p->ig_object->cmds);
X_FREE(p->ig_object);
}
}
void hdmv_vm_free(HDMV_VM **p)
{
if (p && *p) {
bd_mutex_destroy(&(*p)->mutex);
mobj_free(&(*p)->movie_objects);
_free_ig_object(*p);
X_FREE(*p);
}
}
/*
* suspend/resume ("function call")
*/
static int _suspended_at_play_pl(HDMV_VM *p)
{
int play_pl = 0;
if (p && p->suspended_object) {
MOBJ_CMD *cmd = &p->suspended_object->cmds[p->suspended_pc];
HDMV_INSN *insn = &cmd->insn;
play_pl = (insn->grp == INSN_GROUP_BRANCH &&
insn->sub_grp == BRANCH_PLAY &&
( insn->branch_opt == INSN_PLAY_PL ||
insn->branch_opt == INSN_PLAY_PL_PI ||
insn->branch_opt == INSN_PLAY_PL_PM));
}
return play_pl;
}
static int _suspend_for_play_pl(HDMV_VM *p)
{
if (p->playing_object) {
BD_DEBUG(DBG_HDMV|DBG_CRIT, "_suspend_for_play_pl(): object already playing playlist !\n");
return -1;
}
p->playing_object = p->object;
p->playing_pc = p->pc;
p->object = NULL;
return 0;
}
static int _resume_from_play_pl(HDMV_VM *p)
{
if (!p->playing_object) {
BD_DEBUG(DBG_HDMV|DBG_CRIT, "_resume_from_play_pl(): object not playing playlist !\n");
return -1;
}
p->object = p->playing_object;
p->pc = p->playing_pc + 1;
p->playing_object = NULL;
_free_ig_object(p);
return 0;
}
static void _suspend_object(HDMV_VM *p, int psr_backup)
{
BD_DEBUG(DBG_HDMV, "_suspend_object()\n");
if (p->suspended_object) {
BD_DEBUG(DBG_HDMV, "_suspend_object: object already suspended !\n");
// [execute the call, discard old suspended object (10.2.4.2.2)].
}
if (psr_backup) {
bd_psr_save_state(p->regs);
}
if (p->ig_object) {
if (!p->playing_object) {
BD_DEBUG(DBG_HDMV|DBG_CRIT, "_suspend_object: IG object tries to suspend, no playing object !\n");
return;
}
p->suspended_object = p->playing_object;
p->suspended_pc = p->playing_pc;
p->playing_object = NULL;
} else {
if (p->playing_object) {
BD_DEBUG(DBG_HDMV|DBG_CRIT, "_suspend_object: Movie object tries to suspend, also playing object present !\n");
return;
}
p->suspended_object = p->object;
p->suspended_pc = p->pc;
}
p->object = NULL;
_free_ig_object(p);
}
static int _resume_object(HDMV_VM *p, int psr_restore)
{
if (!p->suspended_object) {
BD_DEBUG(DBG_HDMV|DBG_CRIT, "_resume_object: no suspended object!\n");
return -1;
}
p->object = NULL;
p->playing_object = NULL;
_free_ig_object(p);
if (psr_restore) {
/* check if suspended in play_pl */
if (_suspended_at_play_pl(p)) {
BD_DEBUG(DBG_HDMV, "resuming playlist playback\n");
p->playing_object = p->suspended_object;
p->playing_pc = p->suspended_pc;
p->suspended_object = NULL;
bd_psr_restore_state(p->regs);
return 0;
}
bd_psr_restore_state(p->regs);
}
p->object = p->suspended_object;
p->pc = p->suspended_pc + 1;
p->suspended_object = NULL;
BD_DEBUG(DBG_HDMV, "resuming object %ld at %d\n",
(long)(p->object - p->movie_objects->objects),
p->pc);
_queue_event(p, HDMV_EVENT_PLAY_STOP, 0);
return 0;
}
/*
* branching
*/
static int _is_valid_title(HDMV_VM *p, uint32_t title)
{
if (title == 0) {
return p->have_top_menu;
}
if (title == 0xffff) {
return p->have_first_play;
}
return title > 0 && title <= p->num_titles;
}
static int _jump_object(HDMV_VM *p, uint32_t object)
{
if (object >= p->movie_objects->num_objects) {
BD_DEBUG(DBG_HDMV|DBG_CRIT, "_jump_object(): invalid object %u\n", object);
return -1;
}
BD_DEBUG(DBG_HDMV, "_jump_object(): jumping to object %u\n", object);
_queue_event(p, HDMV_EVENT_PLAY_STOP, 0);
_free_ig_object(p);
p->playing_object = NULL;
p->pc = 0;
p->object = &p->movie_objects->objects[object];
/* suspended object is not discarded */
return 0;
}
static int _jump_title(HDMV_VM *p, uint32_t title)
{
if (_is_valid_title(p, title)) {
BD_DEBUG(DBG_HDMV, "_jump_title(%u)\n", title);
/* discard suspended object */
p->suspended_object = NULL;
p->playing_object = NULL;
bd_psr_reset_backup_registers(p->regs);
_queue_event(p, HDMV_EVENT_TITLE, title);
return 0;
}
BD_DEBUG(DBG_HDMV|DBG_CRIT, "_jump_title(%u): invalid title number\n", title);
return -1;
}
static int _call_object(HDMV_VM *p, uint32_t object)
{
if (object >= p->movie_objects->num_objects) {
BD_DEBUG(DBG_HDMV|DBG_CRIT, "_call_object(): invalid object %u\n", object);
return -1;
}
BD_DEBUG(DBG_HDMV, "_call_object(%u)\n", object);
_suspend_object(p, 1);
return _jump_object(p, object);
}
static int _call_title(HDMV_VM *p, uint32_t title)
{
if (_is_valid_title(p, title)) {
BD_DEBUG(DBG_HDMV, "_call_title(%u)\n", title);
_suspend_object(p, 1);
_queue_event(p, HDMV_EVENT_TITLE, title);
return 0;
}
BD_DEBUG(DBG_HDMV|DBG_CRIT, "_call_title(%u): invalid title number\n", title);
return -1;
}
/*
* playback control
*/
static int _play_at(HDMV_VM *p, int playlist, int playitem, int playmark)
{
if (p->ig_object && playlist >= 0) {
BD_DEBUG(DBG_HDMV | DBG_CRIT, "play_at(list %d, item %d, mark %d): "
"playlist change not allowed in interactive composition\n",
playlist, playitem, playmark);
return -1;
}
if (!p->ig_object && playlist < 0) {
BD_DEBUG(DBG_HDMV | DBG_CRIT, "play_at(list %d, item %d, mark %d): "
"playlist not given in movie object (link commands not allowed)\n",
playlist, playitem, playmark);
return -1;
}
BD_DEBUG(DBG_HDMV, "play_at(list %d, item %d, mark %d)\n",
playlist, playitem, playmark);
if (playlist >= 0) {
_queue_event(p, HDMV_EVENT_PLAY_PL, playlist);
_suspend_for_play_pl(p);
}
if (playitem >= 0) {
_queue_event(p, HDMV_EVENT_PLAY_PI, playitem);
}
if (playmark >= 0) {
_queue_event(p, HDMV_EVENT_PLAY_PM, playmark);
}
return 0;
}
static int _play_stop(HDMV_VM *p)
{
if (!p->ig_object) {
BD_DEBUG(DBG_HDMV | DBG_CRIT, "_play_stop() not allowed in movie object\n");
return -1;
}
BD_DEBUG(DBG_HDMV, "_play_stop()\n");
_queue_event(p, HDMV_EVENT_PLAY_STOP, 1);
/* terminate IG object. Continue executing movie object. */
if (_resume_from_play_pl(p) < 0) {
BD_DEBUG(DBG_HDMV|DBG_CRIT, "_play_stop(): resuming movie object failed !\n");
return -1;
}
return 0;
}
/*
* SET/SYSTEM setstream
*/
static void _set_stream(HDMV_VM *p, uint32_t dst, uint32_t src)
{
BD_DEBUG(DBG_HDMV, "_set_stream(0x%x, 0x%x)\n", dst, src);
/* primary audio stream */
if (dst & 0x80000000) {
bd_psr_write(p->regs, PSR_PRIMARY_AUDIO_ID, (dst >> 16) & 0xfff);
}
/* IG stream */
if (src & 0x80000000) {
bd_psr_write(p->regs, PSR_IG_STREAM_ID, (src >> 16) & 0xff);
}
/* angle number */
if (src & 0x8000) {
bd_psr_write(p->regs, PSR_ANGLE_NUMBER, src & 0xff);
}
/* PSR2 */
bd_psr_lock(p->regs);
uint32_t psr2 = bd_psr_read(p->regs, PSR_PG_STREAM);
/* PG TextST stream number */
if (dst & 0x8000) {
uint32_t text_st_num = dst & 0xfff;
psr2 = text_st_num | (psr2 & 0xfffff000);
}
/* Update PG TextST stream display flag */
uint32_t disp_s_flag = (dst & 0x4000) << 17;
psr2 = disp_s_flag | (psr2 & 0x7fffffff);
bd_psr_write(p->regs, PSR_PG_STREAM, psr2);
bd_psr_unlock(p->regs);
}
static void _set_sec_stream(HDMV_VM *p, uint32_t dst, uint32_t src)
{
BD_DEBUG(DBG_HDMV, "_set_sec_stream(0x%x, 0x%x)\n", dst, src);
uint32_t disp_v_flag = (dst >> 30) & 1;
uint32_t disp_a_flag = (src >> 30) & 1;
uint32_t text_st_flags = (src >> 13) & 3;
/* PSR14 */
bd_psr_lock(p->regs);
uint32_t psr14 = bd_psr_read(p->regs, PSR_SECONDARY_AUDIO_VIDEO);
/* secondary video */
if (dst & 0x80000000) {
uint32_t sec_video = dst & 0xff;
psr14 = (sec_video << 8) | (psr14 & 0xffff00ff);
}
/* secondary video size */
if (dst & 0x00800000) {
uint32_t video_size = (dst >> 16) & 0xf;
psr14 = (video_size << 24) | (psr14 & 0xf0ffffff);
}
/* secondary audio */
if (src & 0x80000000) {
uint32_t sec_audio = (src >> 16) & 0xff;
psr14 = sec_audio | (psr14 & 0xffffff00);
}
psr14 = (disp_v_flag << 31) | (psr14 & 0x7fffffff);
psr14 = (disp_a_flag << 30) | (psr14 & 0xbfffffff);
bd_psr_write(p->regs, PSR_SECONDARY_AUDIO_VIDEO, psr14);
/* PSR2 */
uint32_t psr2 = bd_psr_read(p->regs, PSR_PG_STREAM);
/* PiP PG TextST stream */
if (src & 0x8000) {
uint32_t stream = src & 0xfff;
psr2 = (stream << 16) | (psr2 & 0xf000ffff);
}
psr2 = (text_st_flags << 30) | (psr2 & 0x3fffffff);
bd_psr_write(p->regs, PSR_PG_STREAM, psr2);
bd_psr_unlock(p->regs);
}
static void _set_stream_ss(HDMV_VM *p, uint32_t dst, uint32_t src)
{
BD_DEBUG(DBG_HDMV, "_set_stream_ss(0x%x, 0x%x)\n", dst, src);
if (!(bd_psr_read(p->regs, PSR_3D_STATUS) & 1)) {
BD_DEBUG(DBG_HDMV, "_set_stream_ss ignored (PSR22 indicates 2D mode)\n");
return;
}
BD_DEBUG(DBG_HDMV, "_set_stream_ss(0x%x, 0x%x) unimplemented\n", dst, src);
}
static void _setsystem_0x10(HDMV_VM *p, uint32_t dst, uint32_t src)
{
BD_DEBUG(DBG_HDMV, "_set_psr103(0x%x, 0x%x)\n", dst, src);
bd_psr_lock(p->regs);
/* just a guess ... */
//bd_psr_write(p->regs, 104, 0);
bd_psr_write(p->regs, 103, dst);
bd_psr_unlock(p->regs);
}
/*
* SET/SYSTEM navigation control
*/
static void _set_button_page(HDMV_VM *p, uint32_t dst, uint32_t src)
{
if (p->ig_object) {
uint32_t param;
param = (src & 0xc0000000) | /* page and effects flags */
((dst & 0x80000000) >> 2) | /* button flag */
((src & 0x000000ff) << 16) | /* page id */
(dst & 0x0000ffff); /* button id */
_queue_event(p, HDMV_EVENT_SET_BUTTON_PAGE, param);
/* terminate */
p->pc = 1 << 17;
return;
}
/* selected button */
if (dst & 0x80000000) {
bd_psr_write(p->regs, PSR_SELECTED_BUTTON_ID, dst & 0xffff);
}
/* active page */
if (src & 0x80000000) {
bd_psr_write(p->regs, PSR_MENU_PAGE_ID, src & 0xff);
}
}
static void _enable_button(HDMV_VM *p, uint32_t dst, int enable)
{
/* not valid in movie objects */
if (p->ig_object) {
if (enable) {
_queue_event(p, HDMV_EVENT_ENABLE_BUTTON, dst);
} else {
_queue_event(p, HDMV_EVENT_DISABLE_BUTTON, dst);
}
}
}
static void _set_still_mode(HDMV_VM *p, int enable)
{
/* not valid in movie objects */
if (p->ig_object) {
_queue_event(p, HDMV_EVENT_STILL, enable);
}
}
static void _popup_off(HDMV_VM *p)
{
/* not valid in movie objects */
if (p->ig_object) {
_queue_event(p, HDMV_EVENT_POPUP_OFF, 1);
}
}
/*
* SET/SYSTEM 3D mode
*/
static void _set_output_mode(HDMV_VM *p, uint32_t dst)
{
if ((bd_psr_read(p->regs, PSR_PROFILE_VERSION) & 0x130240) != 0x130240) {
BD_DEBUG(DBG_HDMV, "_set_output_mode ignored (not running as profile 5 player)\n");
return;
}
bd_psr_lock(p->regs);
uint32_t psr22 = bd_psr_read(p->regs, PSR_3D_STATUS);
/* update output mode (bit 0). PSR22 bits 1 and 2 are subtitle alignment (_set_stream_ss()) */
if (dst & 1) {
psr22 |= 1;
} else {
psr22 &= ~1;
}
bd_psr_write(p->regs, PSR_3D_STATUS, psr22);
bd_psr_unlock(p->regs);
}
/*
* navigation timer
*/
static void _set_nv_timer(HDMV_VM *p, uint32_t dst, uint32_t src)
{
uint32_t mobj_id = dst & 0xffff;
uint32_t timeout = src & 0xffff;
if (!timeout) {
/* cancel timer */
p->nv_timer.time = 0;
bd_psr_write(p->regs, PSR_NAV_TIMER, 0);
return;
}
/* validate params */
if (mobj_id >= p->movie_objects->num_objects) {
BD_DEBUG(DBG_HDMV|DBG_CRIT, "_set_nv_timer(): invalid object id (%d) !\n", mobj_id);
return;
}
if (timeout > 300) {
BD_DEBUG(DBG_HDMV|DBG_CRIT, "_set_nv_timer(): invalid timeout (%d) !\n", timeout);
return;
}
BD_DEBUG(DBG_HDMV | DBG_CRIT, "_set_nv_timer(): navigation timer not implemented !\n");
/* set expiration time */
p->nv_timer.time = time(NULL);
p->nv_timer.time += timeout;
p->nv_timer.mobj_id = mobj_id;
bd_psr_write(p->regs, PSR_NAV_TIMER, timeout);
}
/* Unused function.
* Commenting out to disable "‘_check_nv_timer’ defined but not used" warning
static int _check_nv_timer(HDMV_VM *p)
{
if (p->nv_timer.time > 0) {
time_t now = time(NULL);
if (now >= p->nv_timer.time) {
BD_DEBUG(DBG_HDMV, "navigation timer expired, jumping to object %d\n", p->nv_timer.mobj_id);
bd_psr_write(p->regs, PSR_NAV_TIMER, 0);
p->nv_timer.time = 0;
_jump_object(p, p->nv_timer.mobj_id);
return 0;
}
bd_psr_write(p->regs, PSR_NAV_TIMER, (p->nv_timer.time - now));
}
return -1;
}
*/
/*
* trace
*/
static void _hdmv_trace_cmd(int pc, MOBJ_CMD *cmd)
{
if (bd_get_debug_mask() & DBG_HDMV) {
char buf[384], *dst = buf;
dst += sprintf(dst, "%04d: ", pc);
/*dst +=*/ mobj_sprint_cmd(dst, cmd);
BD_DEBUG(DBG_HDMV, "%s\n", buf);
}
}
static void _hdmv_trace_res(uint32_t new_src, uint32_t new_dst, uint32_t orig_src, uint32_t orig_dst)
{
if (bd_get_debug_mask() & DBG_HDMV) {
if (new_dst != orig_dst || new_src != orig_src) {
char buf[384], *dst = buf;
dst += sprintf(dst, " : [");
if (new_dst != orig_dst) {
dst += sprintf(dst, " dst 0x%x <== 0x%x ", orig_dst, new_dst);
}
if (new_src != orig_src) {
dst += sprintf(dst, " src 0x%x <== 0x%x ", orig_src, new_src);
}
/*dst +=*/ sprintf(dst, "]");
BD_DEBUG(DBG_HDMV, "%s\n", buf);
}
}
}
/*
* interpreter
*/
/*
* tools
*/
#define SWAP_u32(a, b) do { uint32_t tmp = a; a = b; b = tmp; } while(0)
static inline uint32_t RAND_u32(HDMV_VM *p, uint32_t range)
{
p->rand = p->rand * UINT64_C(6364136223846793005) + UINT64_C(1);
if (range == 0) {
BD_DEBUG(DBG_HDMV|DBG_CRIT, "RAND_u32: invalid range (0)\n");
return 1;
}
return ((uint32_t)(p->rand >> 32)) % range + 1;
}
static inline uint32_t ADD_u32(uint32_t a, uint32_t b)
{
/* overflow -> saturate */
uint64_t result = (uint64_t)a + b;
return result < 0xffffffff ? (uint32_t)result : 0xffffffff;
}
static inline uint32_t MUL_u32(uint32_t a, uint32_t b)
{
/* overflow -> saturate */
uint64_t result = (uint64_t)a * b;
return result < 0xffffffff ? (uint32_t)result : 0xffffffff;
}
/*
* _hdmv_step()
* - execute next instruction from current program
*/
static int _hdmv_step(HDMV_VM *p)
{
MOBJ_CMD *cmd = &p->object->cmds[p->pc];
HDMV_INSN *insn = &cmd->insn;
uint32_t src = 0;
uint32_t dst = 0;
int inc_pc = 1;
/* fetch operand values */
_fetch_operands(p, cmd, &dst, &src);
/* trace */
_hdmv_trace_cmd(p->pc, cmd);
/* execute */
switch (insn->grp) {
case INSN_GROUP_BRANCH:
switch (insn->sub_grp) {
case BRANCH_GOTO:
if (insn->op_cnt > 1) {
BD_DEBUG(DBG_HDMV|DBG_CRIT, "too many operands in BRANCH/GOTO opcode 0x%08x\n", *(uint32_t*)insn);
}
switch (insn->branch_opt) {
case INSN_NOP: break;
case INSN_GOTO: p->pc = dst - 1; break;
case INSN_BREAK: p->pc = 1 << 17; break;
default:
BD_DEBUG(DBG_HDMV|DBG_CRIT, "unknown BRANCH/GOTO option %d in opcode 0x%08x\n",
insn->branch_opt, *(uint32_t*)insn);
break;
}
break;
case BRANCH_JUMP:
if (insn->op_cnt > 1) {
BD_DEBUG(DBG_HDMV|DBG_CRIT, "too many operands in BRANCH/JUMP opcode 0x%08x\n", *(uint32_t*)insn);
}
switch (insn->branch_opt) {
case INSN_JUMP_TITLE: _jump_title(p, dst); break;
case INSN_CALL_TITLE: _call_title(p, dst); break;
case INSN_RESUME: _resume_object(p, 1); break;
case INSN_JUMP_OBJECT: if (!_jump_object(p, dst)) { inc_pc = 0; } break;
case INSN_CALL_OBJECT: if (!_call_object(p, dst)) { inc_pc = 0; } break;
default:
BD_DEBUG(DBG_HDMV|DBG_CRIT, "unknown BRANCH/JUMP option %d in opcode 0x%08x\n",
insn->branch_opt, *(uint32_t*)insn);
break;
}
break;
case BRANCH_PLAY:
switch (insn->branch_opt) {
case INSN_PLAY_PL: _play_at(p, dst, -1, -1); break;
case INSN_PLAY_PL_PI: _play_at(p, dst, src, -1); break;
case INSN_PLAY_PL_PM: _play_at(p, dst, -1, src); break;
case INSN_LINK_PI: _play_at(p, -1, dst, -1); break;
case INSN_LINK_MK: _play_at(p, -1, -1, dst); break;
case INSN_TERMINATE_PL: if (!_play_stop(p)) { inc_pc = 0; } break;
default:
BD_DEBUG(DBG_HDMV|DBG_CRIT, "unknown BRANCH/PLAY option %d in opcode 0x%08x\n",
insn->branch_opt, *(uint32_t*)insn);
break;
}
break;
default:
BD_DEBUG(DBG_HDMV|DBG_CRIT, "unknown BRANCH subgroup %d in opcode 0x%08x\n",
insn->sub_grp, *(uint32_t*)insn);
break;
}
break; /* INSN_GROUP_BRANCH */
case INSN_GROUP_CMP:
if (insn->op_cnt < 2) {
BD_DEBUG(DBG_HDMV|DBG_CRIT, "missing operand in BRANCH/JUMP opcode 0x%08x\n", *(uint32_t*)insn);
}
switch (insn->cmp_opt) {
case INSN_BC: p->pc += !!(dst & ~src); break;
case INSN_EQ: p->pc += !(dst == src); break;
case INSN_NE: p->pc += !(dst != src); break;
case INSN_GE: p->pc += !(dst >= src); break;
case INSN_GT: p->pc += !(dst > src); break;
case INSN_LE: p->pc += !(dst <= src); break;
case INSN_LT: p->pc += !(dst < src); break;
default:
BD_DEBUG(DBG_HDMV|DBG_CRIT, "unknown COMPARE option %d in opcode 0x%08x\n",
insn->cmp_opt, *(uint32_t*)insn);
break;
}
break; /* INSN_GROUP_CMP */
case INSN_GROUP_SET:
switch (insn->sub_grp) {
case SET_SET: {
uint32_t src0 = src;
uint32_t dst0 = dst;
if (insn->op_cnt < 2) {
BD_DEBUG(DBG_HDMV|DBG_CRIT, "missing operand in SET/SET opcode 0x%08x\n", *(uint32_t*)insn);
}
switch (insn->set_opt) {
case INSN_MOVE: dst = src; break;
case INSN_SWAP: SWAP_u32(src, dst); break;
case INSN_SUB: dst = dst > src ? dst - src : 0; break;
case INSN_DIV: dst = src > 0 ? dst / src : 0xffffffff; break;
case INSN_MOD: dst = src > 0 ? dst % src : 0xffffffff; break;
case INSN_ADD: dst = ADD_u32(src, dst); break;
case INSN_MUL: dst = MUL_u32(dst, src); break;
case INSN_RND: dst = RAND_u32(p, src); break;
case INSN_AND: dst &= src; break;
case INSN_OR: dst |= src; break;
case INSN_XOR: dst ^= src; break;
case INSN_BITSET: dst |= (1 << src); break;
case INSN_BITCLR: dst &= ~(1 << src); break;
case INSN_SHL: dst <<= src; break;
case INSN_SHR: dst >>= src; break;
default:
BD_DEBUG(DBG_HDMV|DBG_CRIT, "unknown SET option %d in opcode 0x%08x\n",
insn->set_opt, *(uint32_t*)insn);
break;
}
/* store result(s) */
if (dst != dst0 || src != src0) {
_hdmv_trace_res(src, dst, src0, dst0);
_store_result(p, cmd, src, dst, src0, dst0);
}
break;
}
case SET_SETSYSTEM:
switch (insn->set_opt) {
case INSN_SET_STREAM: _set_stream (p, dst, src); break;
case INSN_SET_SEC_STREAM: _set_sec_stream (p, dst, src); break;
case INSN_SET_NV_TIMER: _set_nv_timer (p, dst, src); break;
case INSN_SET_BUTTON_PAGE: _set_button_page(p, dst, src); break;
case INSN_ENABLE_BUTTON: _enable_button (p, dst, 1); break;
case INSN_DISABLE_BUTTON: _enable_button (p, dst, 0); break;
case INSN_POPUP_OFF: _popup_off (p); break;
case INSN_STILL_ON: _set_still_mode (p, 1); break;
case INSN_STILL_OFF: _set_still_mode (p, 0); break;
case INSN_SET_OUTPUT_MODE: _set_output_mode(p, dst); break;
case INSN_SET_STREAM_SS: _set_stream_ss (p, dst, src); break;
case INSN_SETSYSTEM_0x10: _setsystem_0x10 (p, dst, src); break;
default:
BD_DEBUG(DBG_HDMV|DBG_CRIT, "unknown SETSYSTEM option %d in opcode 0x%08x\n", insn->set_opt, *(uint32_t*)insn);
break;
}
break;
default:
BD_DEBUG(DBG_HDMV|DBG_CRIT, "unknown SET subgroup %d in opcode 0x%08x\n",
insn->sub_grp, *(uint32_t*)insn);
break;
}
break; /* INSN_GROUP_SET */
default:
BD_DEBUG(DBG_HDMV|DBG_CRIT, "unknown operation group %d in opcode 0x%08x\n",
insn->grp, *(uint32_t*)insn);
break;
}
/* inc program counter to next instruction */
p->pc += inc_pc;
return 0;
}
/*
* interface
*/
int hdmv_vm_select_object(HDMV_VM *p, uint32_t object)
{
int result;
if (!p) {
return -1;
}
bd_mutex_lock(&p->mutex);
result = _jump_object(p, object);
bd_mutex_unlock(&p->mutex);
return result;
}
static int _set_object(HDMV_VM *p, int num_nav_cmds, void *nav_cmds)
{
MOBJ_OBJECT *ig_object = calloc(1, sizeof(MOBJ_OBJECT));
if (!ig_object) {
BD_DEBUG(DBG_CRIT, "out of memory\n");
return -1;
}
ig_object->num_cmds = num_nav_cmds;
ig_object->cmds = calloc(num_nav_cmds, sizeof(MOBJ_CMD));
if (!ig_object->cmds) {
BD_DEBUG(DBG_CRIT, "out of memory\n");
X_FREE(ig_object);
return -1;
}
memcpy(ig_object->cmds, nav_cmds, num_nav_cmds * sizeof(MOBJ_CMD));
p->pc = 0;
p->ig_object = ig_object;
p->object = ig_object;
return 0;
}
int hdmv_vm_set_object(HDMV_VM *p, int num_nav_cmds, void *nav_cmds)
{
int result = -1;
if (!p) {
return -1;
}
bd_mutex_lock(&p->mutex);
p->object = NULL;
_free_ig_object(p);
if (nav_cmds && num_nav_cmds > 0) {
result = _set_object(p, num_nav_cmds, nav_cmds);
}
bd_mutex_unlock(&p->mutex);
return result;
}
int hdmv_vm_get_event(HDMV_VM *p, HDMV_EVENT *ev)
{
int result;
bd_mutex_lock(&p->mutex);
result = _get_event(p, ev);
bd_mutex_unlock(&p->mutex);
return result;
}
int hdmv_vm_running(HDMV_VM *p)
{
int result;
if (!p) {
return 0;
}
bd_mutex_lock(&p->mutex);
result = !!p->object;
bd_mutex_unlock(&p->mutex);
return result;
}
uint32_t hdmv_vm_get_uo_mask(HDMV_VM *p)
{
uint32_t mask = 0;
const MOBJ_OBJECT *o = NULL;
if (!p) {
return 0;
}
bd_mutex_lock(&p->mutex);
if ((o = (p->object && !p->ig_object) ? p->object : (p->playing_object ? p->playing_object : p->suspended_object))) {
mask |= o->menu_call_mask;
mask |= o->title_search_mask << 1;
}
bd_mutex_unlock(&p->mutex);
return mask;
}
int hdmv_vm_resume(HDMV_VM *p)
{
int result;
if (!p) {
return -1;
}
bd_mutex_lock(&p->mutex);
result = _resume_from_play_pl(p);
bd_mutex_unlock(&p->mutex);
return result;
}
int hdmv_vm_suspend_pl(HDMV_VM *p)
{
int result = -1;
if (!p) {
return -1;
}
bd_mutex_lock(&p->mutex);
if (p->object || p->ig_object) {
BD_DEBUG(DBG_HDMV, "hdmv_vm_suspend_pl(): HDMV VM is still running\n");
} else if (!p->playing_object) {
BD_DEBUG(DBG_HDMV, "hdmv_vm_suspend_pl(): No playing object\n");
} else if (!p->playing_object->resume_intention_flag) {
BD_DEBUG(DBG_HDMV, "hdmv_vm_suspend_pl(): no resume intention flag\n");
p->playing_object = NULL;
result = 0;
} else {
p->suspended_object = p->playing_object;
p->suspended_pc = p->playing_pc;
p->playing_object = NULL;
bd_psr_save_state(p->regs);
result = 0;
}
bd_mutex_unlock(&p->mutex);
return result;
}
/* terminate program after MAX_LOOP instructions */
#define MAX_LOOP 1000000
static int _vm_run(HDMV_VM *p, HDMV_EVENT *ev)
{
int max_loop = MAX_LOOP;
/* pending events ? */
if (!_get_event(p, ev)) {
return 0;
}
/* valid program ? */
if (!p->object) {
BD_DEBUG(DBG_HDMV|DBG_CRIT, "hdmv_vm_run(): no object selected\n");
return -1;
}
while (--max_loop > 0) {
/* suspended ? */
if (!p->object) {
BD_DEBUG(DBG_HDMV, "hdmv_vm_run(): object suspended\n");
_get_event(p, ev);
return 0;
}
/* terminated ? */
if (p->pc >= p->object->num_cmds) {
BD_DEBUG(DBG_HDMV, "terminated with PC=%d\n", p->pc);
p->object = NULL;
ev->event = HDMV_EVENT_END;
if (p->ig_object) {
ev->event = HDMV_EVENT_IG_END;
_free_ig_object(p);
}
return 0;
}
/* next instruction */
if (_hdmv_step(p) < 0) {
p->object = NULL;
return -1;
}
/* events ? */
if (!_get_event(p, ev)) {
return 0;
}
}
BD_DEBUG(DBG_HDMV|DBG_CRIT, "hdmv_vm: infinite program ? terminated after %d instructions.\n", MAX_LOOP);
p->object = NULL;
return -1;
}
int hdmv_vm_run(HDMV_VM *p, HDMV_EVENT *ev)
{
int result;
if (!p) {
return -1;
}
bd_mutex_lock(&p->mutex);
result = _vm_run(p, ev);
bd_mutex_unlock(&p->mutex);
return result;
}