hdmv_vm.c 29.1 KB
Newer Older
hpi1's avatar
hpi1 committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
/*
 * This file is part of libbluray
 * Copyright (C) 2010  hpi1
 *
 * 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/>.
 */

#include "hdmv_vm.h"

#include "mobj_parse.h"
#include "hdmv_insn.h"
#include "../register.h"

26
#include "../bdnav/index_parse.h"
hpi1's avatar
hpi1 committed
27 28 29
#include "util/macro.h"
#include "util/strutl.h"
#include "util/logging.h"
hpi1's avatar
hpi1 committed
30
#include "util/mutex.h"
hpi1's avatar
hpi1 committed
31 32 33

#include <stdlib.h>
#include <string.h>
34
#include <time.h>
hpi1's avatar
hpi1 committed
35 36


hpi1's avatar
hpi1 committed
37
typedef struct {
38
    time_t   time;
hpi1's avatar
hpi1 committed
39 40 41
    uint32_t mobj_id;
} NV_TIMER;

hpi1's avatar
hpi1 committed
42
struct hdmv_vm_s {
hpi1's avatar
hpi1 committed
43 44 45

    BD_MUTEX       mutex;

hpi1's avatar
hpi1 committed
46 47 48 49 50 51 52
    /* state */
    uint32_t       pc;            /* program counter */
    BD_REGISTERS  *regs;          /* player registers */
    MOBJ_OBJECT   *object;        /* currently running object code */

    HDMV_EVENT     event[5];      /* pending events to return */

hpi1's avatar
hpi1 committed
53 54
    NV_TIMER       nv_timer;      /* navigation timer */

hpi1's avatar
hpi1 committed
55 56 57 58 59 60 61
    /* movie objects */
    MOBJ_OBJECTS  *movie_objects; /* disc movie objects */
    MOBJ_OBJECT   *ig_object;     /* current object from IG stream */

    /* suspended object */
    MOBJ_OBJECT *suspended_object;
    int          suspended_pc;
62 63 64

    /* disc index (used to verify CALL_TITLE/JUMP_TITLE) */
    INDX_ROOT   *indx;
hpi1's avatar
hpi1 committed
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
};

/*
 * 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)) {
90
        BD_DEBUG(DBG_HDMV, "_store_reg(): invalid register 0x%x\n", reg);
hpi1's avatar
hpi1 committed
91 92 93 94
        return -1;
    }

    if (reg & PSR_FLAG) {
95
        BD_DEBUG(DBG_HDMV, "_store_reg(): storing to PSR is not allowed\n");
96
        return -1;
hpi1's avatar
hpi1 committed
97 98 99 100 101 102 103 104
    }  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)) {
105
        BD_DEBUG(DBG_HDMV, "_read_reg(): invalid register 0x%x\n", reg);
hpi1's avatar
hpi1 committed
106 107 108 109 110 111 112 113 114 115
        return 0;
    }

    if (reg & PSR_FLAG) {
        return bd_psr_read(p->regs, reg & 0x7f);
    } else {
        return bd_gpr_read(p->regs, reg);
    }
}

116 117 118 119 120 121 122 123 124 125 126 127
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);
}

128 129 130 131 132 133 134 135 136 137
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;
}

hpi1's avatar
hpi1 committed
138 139 140 141 142 143 144
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) {
145
            BD_DEBUG(DBG_HDMV|DBG_CRIT, "ERROR: storing to imm ! ");
hpi1's avatar
hpi1 committed
146 147 148 149 150 151 152
            return -1;
        }
        ret = _store_reg(p, cmd->dst, dst);
    }

    if (src != src0) {
        if (cmd->insn.imm_op1) {
153
            BD_DEBUG(DBG_HDMV|DBG_CRIT, "ERROR: storing to imm ! ");
hpi1's avatar
hpi1 committed
154 155 156 157 158 159 160 161
            return -1;
        }
        ret += _store_reg(p, cmd->src, src);
    }

    return ret;
}

162
static uint32_t _fetch_operand(HDMV_VM *p, int setstream, int setbuttonpage, int imm, uint32_t value)
163
{
hpi1's avatar
hpi1 committed
164 165 166
    if (imm) {
        return value;
    }
167

hpi1's avatar
hpi1 committed
168 169
    if (setstream) {
        return _read_setstream_regs(p, value);
170

171
    } else if (setbuttonpage) {
hpi1's avatar
hpi1 committed
172
        return _read_setbuttonpage_reg(p, value);
173

174
    } else {
hpi1's avatar
hpi1 committed
175
        return _read_reg(p, value);
176 177 178 179 180 181 182 183 184 185 186
    }
}

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));
187 188 189
    int setbuttonpage = (insn->grp     == INSN_GROUP_SET &&
                         insn->sub_grp == SET_SETSYSTEM  &&
                         insn->set_opt == INSN_SET_BUTTON_PAGE);
190 191 192 193

    *dst = *src = 0;

    if (insn->op_cnt > 0) {
194
        *dst = _fetch_operand(p, setstream, setbuttonpage, insn->imm_op1, cmd->dst);
195 196 197
    }

    if (insn->op_cnt > 1) {
198
        *src = _fetch_operand(p, setstream, setbuttonpage, insn->imm_op2, cmd->src);
199 200 201
    }
}

hpi1's avatar
hpi1 committed
202 203 204 205 206 207 208 209 210 211 212 213
/*
 * 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;
    }

hpi1's avatar
hpi1 committed
214 215
    ev->event = HDMV_EVENT_NONE;

hpi1's avatar
hpi1 committed
216 217 218
    return -1;
}

hpi1's avatar
hpi1 committed
219
static int _queue_event(HDMV_VM *p, uint32_t event, uint32_t param)
hpi1's avatar
hpi1 committed
220 221 222
{
    unsigned i;
    for (i = 0; i < sizeof(p->event) / sizeof(p->event[0]) - 1; i++) {
hpi1's avatar
hpi1 committed
223
        if (p->event[i].event == HDMV_EVENT_NONE) {
hpi1's avatar
hpi1 committed
224 225
            p->event[i].event = event;
            p->event[i].param = param;
hpi1's avatar
hpi1 committed
226 227 228 229
            return 0;
        }
    }

230
    BD_DEBUG(DBG_HDMV|DBG_CRIT, "_queue_event(%d, %d): queue overflow !\n", event, param);
hpi1's avatar
hpi1 committed
231 232 233 234 235 236 237
    return -1;
}

/*
 * vm init
 */

238
HDMV_VM *hdmv_vm_init(const char *disc_root, BD_REGISTERS *regs, INDX_ROOT *indx)
hpi1's avatar
hpi1 committed
239 240 241 242 243 244 245 246 247 248 249 250 251 252
{
    HDMV_VM *p = calloc(1, sizeof(HDMV_VM));
    char *file;

    /* read movie objects */
    file = str_printf("%s/BDMV/MovieObject.bdmv", disc_root);
    p->movie_objects = mobj_parse(file);
    X_FREE(file);
    if (!p->movie_objects) {
        X_FREE(p);
        return NULL;
    }

    p->regs         = regs;
253
    p->indx         = indx;
hpi1's avatar
hpi1 committed
254

hpi1's avatar
hpi1 committed
255 256
    bd_mutex_init(&p->mutex);

hpi1's avatar
hpi1 committed
257 258 259
    return  p;
}

hpi1's avatar
hpi1 committed
260 261 262 263 264 265 266 267
static void _free_ig_object(HDMV_VM *p)
{
    if (p->ig_object) {
        X_FREE(p->ig_object->cmds);
        X_FREE(p->ig_object);
    }
}

268
void hdmv_vm_free(HDMV_VM **p)
hpi1's avatar
hpi1 committed
269
{
270
    if (p && *p) {
hpi1's avatar
hpi1 committed
271

hpi1's avatar
hpi1 committed
272 273
        bd_mutex_destroy(&(*p)->mutex);

hpi1's avatar
hpi1 committed
274
        mobj_free(&(*p)->movie_objects);
275

hpi1's avatar
hpi1 committed
276
        _free_ig_object(*p);
hpi1's avatar
hpi1 committed
277

278 279
        X_FREE(*p);
    }
hpi1's avatar
hpi1 committed
280 281 282 283 284 285
}

/*
 * suspend/resume ("function call")
 */

286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301
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;
}

302
static void _suspend_object(HDMV_VM *p, int psr_backup)
hpi1's avatar
hpi1 committed
303
{
304
    BD_DEBUG(DBG_HDMV, "_suspend_object()\n");
hpi1's avatar
hpi1 committed
305 306

    if (p->suspended_object) {
307
        BD_DEBUG(DBG_HDMV, "_suspend_object: object already suspended !\n");
hpi1's avatar
hpi1 committed
308 309 310
        // [execute the call, discard old suspended object (10.2.4.2.2)].
    }

311 312 313
    if (psr_backup) {
        bd_psr_save_state(p->regs);
    }
hpi1's avatar
hpi1 committed
314 315 316 317

    p->suspended_object = p->object;
    p->suspended_pc     = p->pc;

318
    p->object = NULL;
hpi1's avatar
hpi1 committed
319 320
}

321
static int _resume_object(HDMV_VM *p, int psr_restore)
hpi1's avatar
hpi1 committed
322 323
{
    if (!p->suspended_object) {
324
        BD_DEBUG(DBG_HDMV|DBG_CRIT, "_resume_object: no suspended object!\n");
hpi1's avatar
hpi1 committed
325 326 327
        return -1;
    }

328 329
    _free_ig_object(p);

hpi1's avatar
hpi1 committed
330
    p->object = p->suspended_object;
331
    p->pc     = p->suspended_pc + 1;
hpi1's avatar
hpi1 committed
332

333 334 335
    if (psr_restore) {
        bd_psr_restore_state(p->regs);
    }
hpi1's avatar
hpi1 committed
336

337
    BD_DEBUG(DBG_HDMV, "resuming object %p at %d\n", p->object, p->pc + 1);
hpi1's avatar
hpi1 committed
338 339 340 341 342 343 344 345 346 347 348

    p->suspended_object = NULL;

    return 0;
}


/*
 * branching
 */

349 350 351 352 353 354 355 356 357 358 359 360 361 362 363
static int _is_valid_title(HDMV_VM *p, int title)
{
    if (title == 0 || title == 0xffff) {
        INDX_PLAY_ITEM *pi = (!title) ? &p->indx->top_menu : &p->indx->first_play;

        if (pi->object_type == indx_object_type_hdmv &&  pi->hdmv.id_ref == 0xffff) {
            /* no top menu or first play title (5.2.3.3) */
            return 0;
        }
        return 1;
    }

    return title > 0 && title <= p->indx->num_titles;
}

hpi1's avatar
hpi1 committed
364 365
static int _jump_object(HDMV_VM *p, int object)
{
366
    if (object < 0 || object >= p->movie_objects->num_objects) {
367
        BD_DEBUG(DBG_HDMV|DBG_CRIT, "_jump_object(): invalid object %d\n", object);
hpi1's avatar
hpi1 committed
368 369 370
        return -1;
    }

371
    BD_DEBUG(DBG_HDMV, "_jump_object(): jumping to object %d\n", object);
hpi1's avatar
hpi1 committed
372

373 374 375 376
    _free_ig_object(p);

    p->pc     = 0;
    p->object = &p->movie_objects->objects[object];
hpi1's avatar
hpi1 committed
377

378 379
    /* suspended object is not discarded */

hpi1's avatar
hpi1 committed
380 381 382 383 384
    return 0;
}

static int _jump_title(HDMV_VM *p, int title)
{
385
    if (_is_valid_title(p, title)) {
386
        BD_DEBUG(DBG_HDMV, "_jump_title(%d)\n", title);
387

388 389 390
        /* discard suspended object */
        p->suspended_object = NULL;
        bd_psr_reset_backup_registers(p->regs);
hpi1's avatar
hpi1 committed
391

hpi1's avatar
hpi1 committed
392
        _queue_event(p, HDMV_EVENT_TITLE, title);
hpi1's avatar
hpi1 committed
393 394 395
        return 0;
    }

396
    BD_DEBUG(DBG_HDMV|DBG_CRIT, "_jump_title(%d): invalid title number\n", title);
hpi1's avatar
hpi1 committed
397

hpi1's avatar
hpi1 committed
398 399 400 401 402
    return -1;
}

static int _call_object(HDMV_VM *p, int object)
{
403
    BD_DEBUG(DBG_HDMV, "_call_object(%d)\n", object);
hpi1's avatar
hpi1 committed
404

405
    _suspend_object(p, 1);
hpi1's avatar
hpi1 committed
406 407 408 409 410 411

    return _jump_object(p, object);
}

static int _call_title(HDMV_VM *p, int title)
{
412
    if (_is_valid_title(p, title)) {
413
        BD_DEBUG(DBG_HDMV, "_call_title(%d)\n", title);
hpi1's avatar
hpi1 committed
414

415
        _suspend_object(p, 1);
hpi1's avatar
hpi1 committed
416

hpi1's avatar
hpi1 committed
417
        _queue_event(p, HDMV_EVENT_TITLE, title);
hpi1's avatar
hpi1 committed
418 419 420
        return 0;
    }

421
    BD_DEBUG(DBG_HDMV|DBG_CRIT, "_call_title(%d): invalid title number\n", title);
hpi1's avatar
hpi1 committed
422

hpi1's avatar
hpi1 committed
423 424 425 426 427 428 429 430 431
    return -1;
}

/*
 * playback control
 */

static int _play_at(HDMV_VM *p, int playlist, int playitem, int playmark)
{
432
    if (p->ig_object && playlist >= 0) {
433
        BD_DEBUG(DBG_HDMV, "play_at(list %d, item %d, mark %d): "
434 435 436 437 438 439
              "playlist change not allowed in interactive composition\n",
              playlist, playitem, playmark);
        return -1;
    }

    if (!p->ig_object && playlist < 0) {
440
        BD_DEBUG(DBG_HDMV, "play_at(list %d, item %d, mark %d): "
441 442 443 444 445
              "playlist not given in movie object (link commands not allowed)\n",
              playlist, playitem, playmark);
        return -1;
    }

446
    BD_DEBUG(DBG_HDMV, "play_at(list %d, item %d, mark %d)\n",
hpi1's avatar
hpi1 committed
447 448
          playlist, playitem, playmark);

hpi1's avatar
hpi1 committed
449
    if (playlist >= 0) {
hpi1's avatar
hpi1 committed
450
        _queue_event(p, HDMV_EVENT_PLAY_PL, playlist);
451
        _suspend_object(p, 0);
hpi1's avatar
hpi1 committed
452 453 454
    }

    if (playitem >= 0) {
hpi1's avatar
hpi1 committed
455
        _queue_event(p, HDMV_EVENT_PLAY_PI, playitem);
hpi1's avatar
hpi1 committed
456 457 458
    }

    if (playmark >= 0) {
hpi1's avatar
hpi1 committed
459
        _queue_event(p, HDMV_EVENT_PLAY_PM, playmark);
hpi1's avatar
hpi1 committed
460 461 462 463 464 465 466
    }

    return 0;
}

static int _play_stop(HDMV_VM *p)
{
467
    if (!p->ig_object) {
468
        BD_DEBUG(DBG_HDMV, "_play_stop() not allowed in movie object\n");
469 470 471
        return -1;
    }

472
    BD_DEBUG(DBG_HDMV, "_play_stop()\n");
hpi1's avatar
hpi1 committed
473
    _queue_event(p, HDMV_EVENT_PLAY_STOP, 0);
hpi1's avatar
hpi1 committed
474 475 476
    return 0;
}

hpi1's avatar
hpi1 committed
477 478 479 480 481 482
/*
 * SET/SYSTEM setstream
 */

static void _set_stream(HDMV_VM *p, uint32_t dst, uint32_t src)
{
483
    BD_DEBUG(DBG_HDMV, "_set_stream(0x%x, 0x%x)\n", dst, src);
hpi1's avatar
hpi1 committed
484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501

    /* 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 */

502 503
    bd_psr_lock(p->regs);

504
    uint32_t psr2 = bd_psr_read(p->regs, PSR_PG_STREAM);
hpi1's avatar
hpi1 committed
505 506 507 508 509 510 511 512 513 514 515

    /* 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);

516
    bd_psr_write(p->regs, PSR_PG_STREAM, psr2);
517 518

    bd_psr_unlock(p->regs);
hpi1's avatar
hpi1 committed
519 520 521 522
}

static void _set_sec_stream(HDMV_VM *p, uint32_t dst, uint32_t src)
{
523
    BD_DEBUG(DBG_HDMV, "_set_sec_stream(0x%x, 0x%x)\n", dst, src);
hpi1's avatar
hpi1 committed
524 525 526 527 528 529 530

    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 */

531 532
    bd_psr_lock(p->regs);

hpi1's avatar
hpi1 committed
533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559
    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 */

560
    uint32_t psr2  = bd_psr_read(p->regs, PSR_PG_STREAM);
hpi1's avatar
hpi1 committed
561 562 563 564 565 566 567 568 569

    /* PiP PG TextST stream */
    if (src & 0x8000) {
        uint32_t stream = src & 0xfff;
        psr2 = (stream << 16) | (psr2 & 0xf000ffff);
    }

    psr2 = (text_st_flags << 30) | (psr2 & 0x3fffffff);

570
    bd_psr_write(p->regs, PSR_PG_STREAM, psr2);
571 572

    bd_psr_unlock(p->regs);
hpi1's avatar
hpi1 committed
573 574 575 576 577 578 579 580 581
}

/*
 * SET/SYSTEM navigation control
 */

static void _set_button_page(HDMV_VM *p, uint32_t dst, uint32_t src)
{
    if (p->ig_object) {
582 583 584 585 586
        uint32_t param;
        param =  (src & 0xc0000000) |        /* page and effects flags */
                ((dst & 0x80000000) >> 2) |  /* button flag */
                ((src & 0x000000ff) << 16) | /* page id */
                 (dst & 0x0000ffff);         /* button id */
hpi1's avatar
hpi1 committed
587

588
         _queue_event(p, HDMV_EVENT_SET_BUTTON_PAGE, param);
hpi1's avatar
hpi1 committed
589

590 591 592
         /* terminate */
         p->pc = 1 << 17;

hpi1's avatar
hpi1 committed
593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645
        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);
    }
}

/*
 * 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 */
646
    p->nv_timer.time = 0;
hpi1's avatar
hpi1 committed
647 648 649

    bd_psr_write(p->regs, PSR_NAV_TIMER, 0);

hpi1's avatar
hpi1 committed
650 651 652 653 654
    return;
  }

  /* validate params */
  if (mobj_id >= p->movie_objects->num_objects) {
655
      BD_DEBUG(DBG_HDMV|DBG_CRIT, "_set_nv_timer(): invalid object id (%d) !\n", mobj_id);
hpi1's avatar
hpi1 committed
656 657 658
      return;
  }
  if (timeout > 300) {
659
      BD_DEBUG(DBG_HDMV|DBG_CRIT, "_set_nv_timer(): invalid timeout (%d) !\n", timeout);
hpi1's avatar
hpi1 committed
660 661 662 663
      return;
  }

  /* set expiration time */
664 665
  p->nv_timer.time = time(NULL);
  p->nv_timer.time += timeout;
hpi1's avatar
hpi1 committed
666 667

  p->nv_timer.mobj_id = mobj_id;
hpi1's avatar
hpi1 committed
668 669

  bd_psr_write(p->regs, PSR_NAV_TIMER, timeout);
hpi1's avatar
hpi1 committed
670 671
}

672 673
/* Unused function.
 * Commenting out to disable "‘_check_nv_timer’ defined but not used" warning
hpi1's avatar
hpi1 committed
674 675
static int _check_nv_timer(HDMV_VM *p)
{
676 677
    if (p->nv_timer.time > 0) {
        time_t now = time(NULL);
hpi1's avatar
hpi1 committed
678

679
        if (now >= p->nv_timer.time) {
680
            BD_DEBUG(DBG_HDMV, "navigation timer expired, jumping to object %d\n", p->nv_timer.mobj_id);
hpi1's avatar
hpi1 committed
681

hpi1's avatar
hpi1 committed
682 683
            bd_psr_write(p->regs, PSR_NAV_TIMER, 0);

684
            p->nv_timer.time = 0;
hpi1's avatar
hpi1 committed
685 686 687 688
            _jump_object(p, p->nv_timer.mobj_id);

            return 0;
        }
hpi1's avatar
hpi1 committed
689

690
        bd_psr_write(p->regs, PSR_NAV_TIMER, (p->nv_timer.time - now));
hpi1's avatar
hpi1 committed
691 692 693 694
    }

    return -1;
}
695
*/
hpi1's avatar
hpi1 committed
696

hpi1's avatar
hpi1 committed
697 698 699 700
/*
 * trace
 */

hpi1's avatar
hpi1 committed
701
static void _hdmv_trace_cmd(int pc, MOBJ_CMD *cmd)
hpi1's avatar
hpi1 committed
702 703 704 705
{
    if (bd_get_debug_mask() & DBG_HDMV) {
        char buf[384], *dst = buf;

706
        dst += sprintf(dst, "%04d:  ", pc);
hpi1's avatar
hpi1 committed
707 708 709

        dst += mobj_sprint_cmd(dst, cmd);

710
        BD_DEBUG(DBG_HDMV, "%s\n", buf);
hpi1's avatar
hpi1 committed
711 712 713 714 715 716 717
    }
}

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) {

hpi1's avatar
hpi1 committed
718
        if (new_dst != orig_dst || new_src != orig_src) {
hpi1's avatar
hpi1 committed
719 720 721
            char buf[384], *dst = buf;

            dst += sprintf(dst, "    :  [");
hpi1's avatar
hpi1 committed
722 723 724 725 726 727
            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);
            }
hpi1's avatar
hpi1 committed
728
            dst += sprintf(dst, "]");
hpi1's avatar
hpi1 committed
729

730
            BD_DEBUG(DBG_HDMV, "%s\n", buf);
hpi1's avatar
hpi1 committed
731
        }
hpi1's avatar
hpi1 committed
732 733 734 735 736 737 738
    }
}

/*
 * interpreter
 */

739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763
/*
 * tools
 */

#define SWAP_u32(a, b) do { uint32_t tmp = a; a = b; b = tmp; } while(0)

static inline uint32_t RAND_u32(uint32_t range)
{
  return range > 0 ? rand() % range + 1 : 0;
}

static inline uint32_t ADD_u32(uint32_t a, uint32_t b)
{
  /* overflow -> saturate */
  uint64_t result = (uint64_t)a + b;
  return result < 0xffffffff ? 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 ? result : 0xffffffff;
}

hpi1's avatar
hpi1 committed
764 765 766 767 768 769 770 771 772 773
/*
 * _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;
774
    int        inc_pc = 1;
hpi1's avatar
hpi1 committed
775 776

    /* fetch operand values */
777
    _fetch_operands(p, cmd, &dst, &src);
hpi1's avatar
hpi1 committed
778

hpi1's avatar
hpi1 committed
779 780 781
    /* trace */
    _hdmv_trace_cmd(p->pc, cmd);

hpi1's avatar
hpi1 committed
782 783 784 785 786 787
    /* execute */
    switch (insn->grp) {
        case INSN_GROUP_BRANCH:
            switch (insn->sub_grp) {
                case BRANCH_GOTO:
                    if (insn->op_cnt > 1) {
788
                        BD_DEBUG(DBG_HDMV|DBG_CRIT, "[too many operands in BRANCH/GOTO opcode 0x%08x] ", *(uint32_t*)insn);
hpi1's avatar
hpi1 committed
789 790 791 792 793 794
                    }
                    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:
795
                            BD_DEBUG(DBG_HDMV|DBG_CRIT, "[unknown BRANCH/GOTO option in opcode 0x%08x] ", *(uint32_t*)insn);
hpi1's avatar
hpi1 committed
796 797 798 799 800
                            break;
                    }
                    break;
                case BRANCH_JUMP:
                    if (insn->op_cnt > 1) {
801
                        BD_DEBUG(DBG_HDMV|DBG_CRIT, "[too many operands in BRANCH/JUMP opcode 0x%08x] ", *(uint32_t*)insn);
hpi1's avatar
hpi1 committed
802 803
                    }
                    switch (insn->branch_opt) {
804 805
                        case INSN_JUMP_TITLE:  _jump_title(p, dst); break;
                        case INSN_CALL_TITLE:  _call_title(p, dst); break;
806
                        case INSN_RESUME:      _resume_object(p, 1);   break;
807 808
                        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;
hpi1's avatar
hpi1 committed
809
                        default:
810
                            BD_DEBUG(DBG_HDMV|DBG_CRIT, "[unknown BRANCH/JUMP option in opcode 0x%08x] ", *(uint32_t*)insn);
hpi1's avatar
hpi1 committed
811 812 813 814 815 816 817 818 819 820 821 822
                            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_TERMINATE_PL: _play_stop(p);              break;
                        case INSN_LINK_PI:      _play_at(p,  -1, dst,  -1); break;
                        case INSN_LINK_MK:      _play_at(p,  -1,  -1, dst); break;
                        default:
823
                            BD_DEBUG(DBG_HDMV|DBG_CRIT, "[unknown BRANCH/PLAY option in opcode 0x%08x] ", *(uint32_t*)insn);
hpi1's avatar
hpi1 committed
824 825 826 827 828
                            break;
                    }
                    break;

                default:
829
                    BD_DEBUG(DBG_HDMV|DBG_CRIT, "[unknown BRANCH subgroup in opcode 0x%08x] ", *(uint32_t*)insn);
hpi1's avatar
hpi1 committed
830 831 832 833 834 835
                    break;
            }
            break; /* INSN_GROUP_BRANCH */

        case INSN_GROUP_CMP:
            if (insn->op_cnt < 2) {
836
                BD_DEBUG(DBG_HDMV|DBG_CRIT, "missing operand in BRANCH/JUMP opcode 0x%08x] ", *(uint32_t*)insn);
hpi1's avatar
hpi1 committed
837 838
            }
            switch (insn->cmp_opt) {
839
                case INSN_BC: p->pc += !!(dst & ~src); break;
hpi1's avatar
hpi1 committed
840 841 842 843 844 845 846
                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:
847
                    BD_DEBUG(DBG_HDMV|DBG_CRIT, "[unknown COMPARE option in opcode 0x%08x] ", *(uint32_t*)insn);
hpi1's avatar
hpi1 committed
848 849 850 851 852 853
                    break;
            }
            break; /* INSN_GROUP_CMP */

        case INSN_GROUP_SET:
            switch (insn->sub_grp) {
hpi1's avatar
hpi1 committed
854 855 856 857
                case SET_SET: {
                    uint32_t src0 = src;
                    uint32_t dst0 = dst;

hpi1's avatar
hpi1 committed
858
                    if (insn->op_cnt < 2) {
859
                        BD_DEBUG(DBG_HDMV|DBG_CRIT, "missing operand in SET/SET opcode 0x%08x] ", *(uint32_t*)insn);
hpi1's avatar
hpi1 committed
860 861 862
                    }
                    switch (insn->set_opt) {
                        case INSN_MOVE:   dst  = src;         break;
863 864
                        case INSN_SWAP:   SWAP_u32(src, dst);   break;
                        case INSN_SUB:    dst  = dst > src ? dst - src :          0; break;
hpi1's avatar
hpi1 committed
865 866
                        case INSN_DIV:    dst  = src > 0   ? dst / src : 0xffffffff; break;
                        case INSN_MOD:    dst  = src > 0   ? dst % src : 0xffffffff; break;
867 868 869
                        case INSN_ADD:    dst  = ADD_u32(src, dst);  break;
                        case INSN_MUL:    dst  = MUL_u32(dst, src);  break;
                        case INSN_RND:    dst  = RAND_u32(src);      break;
hpi1's avatar
hpi1 committed
870 871 872 873 874 875 876 877
                        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:
878
                            BD_DEBUG(DBG_HDMV|DBG_CRIT, "[unknown SET option in opcode 0x%08x] ", *(uint32_t*)insn);
hpi1's avatar
hpi1 committed
879 880 881 882 883
                            break;
                    }

                    /* store result(s) */
                    if (dst != dst0 || src != src0) {
hpi1's avatar
hpi1 committed
884 885 886

                        _hdmv_trace_res(src, dst, src0, dst0);

hpi1's avatar
hpi1 committed
887 888 889
                        _store_result(p, cmd, src, dst, src0, dst0);
                    }
                    break;
hpi1's avatar
hpi1 committed
890
                }
hpi1's avatar
hpi1 committed
891 892
                case SET_SETSYSTEM:
                    switch (insn->set_opt) {
hpi1's avatar
hpi1 committed
893 894 895 896 897 898 899 900 901
                        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;
hpi1's avatar
hpi1 committed
902
                        default:
903
                            BD_DEBUG(DBG_HDMV|DBG_CRIT, "[unknown SETSYSTEM option in opcode 0x%08x] ", *(uint32_t*)insn);
hpi1's avatar
hpi1 committed
904 905 906 907
                            break;
                    }
                    break;
                default:
908
                    BD_DEBUG(DBG_HDMV|DBG_CRIT, "[unknown SET subgroup in opcode 0x%08x] ", *(uint32_t*)insn);
hpi1's avatar
hpi1 committed
909 910 911 912 913
                    break;
            }
            break; /* INSN_GROUP_SET */

        default:
914
            BD_DEBUG(DBG_HDMV|DBG_CRIT, "[unknown group in opcode 0x%08x] ", *(uint32_t*)insn);
hpi1's avatar
hpi1 committed
915 916 917 918
            break;
    }

    /* inc program counter to next instruction */
919
    p->pc += inc_pc;
hpi1's avatar
hpi1 committed
920 921 922 923 924 925 926 927

    return 0;
}

/*
 * interface
 */

hpi1's avatar
hpi1 committed
928
int hdmv_vm_select_object(HDMV_VM *p, int object)
hpi1's avatar
hpi1 committed
929
{
930
    int result;
hpi1's avatar
hpi1 committed
931 932
    bd_mutex_lock(&p->mutex);

933
    result = _jump_object(p, object);
hpi1's avatar
hpi1 committed
934 935

    bd_mutex_unlock(&p->mutex);
936
    return result;
hpi1's avatar
hpi1 committed
937 938 939 940
}

int hdmv_vm_set_object(HDMV_VM *p, int num_nav_cmds, void *nav_cmds)
{
hpi1's avatar
hpi1 committed
941 942 943
    int result = -1;
    bd_mutex_lock(&p->mutex);

hpi1's avatar
hpi1 committed
944 945
    p->object = NULL;

hpi1's avatar
hpi1 committed
946
    _free_ig_object(p);
hpi1's avatar
hpi1 committed
947 948 949 950 951 952 953

    if (nav_cmds && num_nav_cmds > 0) {
        MOBJ_OBJECT *ig_object = calloc(1, sizeof(MOBJ_OBJECT));
        ig_object->num_cmds = num_nav_cmds;
        ig_object->cmds     = calloc(num_nav_cmds, sizeof(MOBJ_CMD));
        memcpy(ig_object->cmds, nav_cmds, num_nav_cmds * sizeof(MOBJ_CMD));

hpi1's avatar
hpi1 committed
954 955 956
        p->pc        = 0;
        p->ig_object = ig_object;
        p->object    = ig_object;
hpi1's avatar
hpi1 committed
957 958

        result = 0;
hpi1's avatar
hpi1 committed
959 960
    }

hpi1's avatar
hpi1 committed
961 962 963
    bd_mutex_unlock(&p->mutex);

    return result;
hpi1's avatar
hpi1 committed
964
}
hpi1's avatar
hpi1 committed
965 966 967

int hdmv_vm_get_event(HDMV_VM *p, HDMV_EVENT *ev)
{
hpi1's avatar
hpi1 committed
968 969 970 971 972 973 974
    int result;
    bd_mutex_lock(&p->mutex);

    result = _get_event(p, ev);

    bd_mutex_unlock(&p->mutex);
    return result;
hpi1's avatar
hpi1 committed
975
}
hpi1's avatar
hpi1 committed
976 977 978

int hdmv_vm_running(HDMV_VM *p)
{
hpi1's avatar
hpi1 committed
979 980 981 982 983 984 985
    int result;
    bd_mutex_lock(&p->mutex);

    result = !!p->object;

    bd_mutex_unlock(&p->mutex);
    return result;
hpi1's avatar
hpi1 committed
986 987
}

988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003
uint32_t hdmv_vm_get_uo_mask(HDMV_VM *p)
{
    uint32_t     mask = 0;
    MOBJ_OBJECT *o    = NULL;

    bd_mutex_lock(&p->mutex);

    if ((o = p->object ? p->object : p->suspended_object)) {
        mask |= o->menu_call_mask;
        mask |= o->title_search_mask << 1;
    }

    bd_mutex_unlock(&p->mutex);
    return mask;
}

hpi1's avatar
hpi1 committed
1004 1005
int hdmv_vm_resume(HDMV_VM *p)
{
hpi1's avatar
hpi1 committed
1006 1007 1008
    int result;
    bd_mutex_lock(&p->mutex);

1009
    result = _resume_object(p, 0);
hpi1's avatar
hpi1 committed
1010 1011 1012

    bd_mutex_unlock(&p->mutex);
    return result;
hpi1's avatar
hpi1 committed
1013 1014
}

1015
int hdmv_vm_suspend_pl(HDMV_VM *p)
hpi1's avatar
hpi1 committed
1016
{
hpi1's avatar
hpi1 committed
1017 1018 1019
    int result = -1;
    bd_mutex_lock(&p->mutex);

1020 1021 1022 1023 1024 1025
    if (p->object || p->ig_object) {
        BD_DEBUG(DBG_HDMV, "hdmv_vm_suspend_pl(): HDMV VM is still running\n");

    } else if (!p->suspended_object) {
        BD_DEBUG(DBG_HDMV, "hdmv_vm_suspend_pl(): No suspended object\n");

1026 1027 1028
    } else if (!_suspended_at_play_pl(p)) {
        BD_DEBUG(DBG_HDMV, "hdmv_vm_suspend_pl(): Object is not playing playlist\n");

1029 1030 1031 1032 1033 1034 1035 1036
    } else if (!p->suspended_object->resume_intention_flag) {
        BD_DEBUG(DBG_HDMV, "hdmv_vm_suspend_pl(): no resume intention flag\n");

        p->suspended_object = NULL;
        result = 0;

    } else {
        bd_psr_save_state(p->regs);
hpi1's avatar
hpi1 committed
1037
        result = 0;
hpi1's avatar
hpi1 committed
1038
    }
hpi1's avatar
hpi1 committed
1039 1040 1041

    bd_mutex_unlock(&p->mutex);
    return result;
hpi1's avatar
hpi1 committed
1042
}
hpi1's avatar
hpi1 committed
1043 1044 1045 1046

/* terminate program after MAX_LOOP instructions */
#define MAX_LOOP 1000000

hpi1's avatar
hpi1 committed
1047
static int _vm_run(HDMV_VM *p, HDMV_EVENT *ev)
hpi1's avatar
hpi1 committed
1048 1049 1050 1051 1052 1053 1054 1055 1056 1057
{
    int max_loop = MAX_LOOP;

    /* pending events ? */
    if (!_get_event(p, ev)) {
        return 0;
    }

    /* valid program ? */
    if (!p->object) {
1058
        BD_DEBUG(DBG_HDMV|DBG_CRIT, "hdmv_vm_run(): no object selected\n");
hpi1's avatar
hpi1 committed
1059 1060 1061 1062 1063 1064 1065
        return -1;
    }

    while (--max_loop > 0) {

        /* terminated ? */
        if (p->pc >= p->object->num_cmds) {
1066
            BD_DEBUG(DBG_HDMV, "terminated with PC=%d\n", p->pc);
hpi1's avatar
hpi1 committed
1067 1068
            p->object = NULL;
            ev->event = HDMV_EVENT_END;
hpi1's avatar
hpi1 committed
1069 1070 1071

            if (p->ig_object) {
                ev->event = HDMV_EVENT_IG_END;
1072
                _free_ig_object(p);
hpi1's avatar
hpi1 committed
1073 1074
            }

hpi1's avatar
hpi1 committed
1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089
            return 0;
        }

        /* next instruction */
        if (_hdmv_step(p) < 0) {
            p->object = NULL;
            return -1;
        }

        /* events ? */
        if (!_get_event(p, ev)) {
            return 0;
        }
    }

1090
    BD_DEBUG(DBG_HDMV|DBG_CRIT, "hdmv_vm: infinite program ? terminated after %d instructions.\n", MAX_LOOP);
hpi1's avatar
hpi1 committed
1091 1092 1093 1094
    p->object = NULL;
    return -1;
}

hpi1's avatar
hpi1 committed
1095 1096 1097 1098 1099 1100
int hdmv_vm_run(HDMV_VM *p, HDMV_EVENT *ev)
{
    int result;
    bd_mutex_lock(&p->mutex);

    result = _vm_run(p, ev);
hpi1's avatar
hpi1 committed
1101

hpi1's avatar
hpi1 committed
1102 1103 1104
    bd_mutex_unlock(&p->mutex);
    return result;
}