nkeynes@87 | 1 | /**
|
nkeynes@561 | 2 | * $Id$
|
nkeynes@87 | 3 | *
|
nkeynes@87 | 4 | * "Fake" BIOS functions, for operation without the actual BIOS.
|
nkeynes@87 | 5 | *
|
nkeynes@1100 | 6 | * Copyright (c) 2005-2010 Nathan Keynes.
|
nkeynes@87 | 7 | *
|
nkeynes@87 | 8 | * This program is free software; you can redistribute it and/or modify
|
nkeynes@87 | 9 | * it under the terms of the GNU General Public License as published by
|
nkeynes@87 | 10 | * the Free Software Foundation; either version 2 of the License, or
|
nkeynes@87 | 11 | * (at your option) any later version.
|
nkeynes@87 | 12 | *
|
nkeynes@87 | 13 | * This program is distributed in the hope that it will be useful,
|
nkeynes@87 | 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
nkeynes@87 | 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
nkeynes@87 | 16 | * GNU General Public License for more details.
|
nkeynes@87 | 17 | */
|
nkeynes@87 | 18 |
|
nkeynes@87 | 19 | #include "dream.h"
|
nkeynes@87 | 20 | #include "mem.h"
|
nkeynes@102 | 21 | #include "syscall.h"
|
nkeynes@1100 | 22 | #include "asic.h"
|
nkeynes@422 | 23 | #include "dreamcast.h"
|
nkeynes@1099 | 24 | #include "bootstrap.h"
|
nkeynes@564 | 25 | #include "sh4/sh4.h"
|
nkeynes@1099 | 26 | #include "drivers/cdrom/cdrom.h"
|
nkeynes@1099 | 27 | #include "drivers/cdrom/isoread.h"
|
nkeynes@1099 | 28 | #include "gdrom/gdrom.h"
|
nkeynes@87 | 29 |
|
nkeynes@1100 | 30 | /* Definitions from KOS */
|
nkeynes@87 | 31 | #define COMMAND_QUEUE_LENGTH 16
|
nkeynes@87 | 32 |
|
nkeynes@1100 | 33 | #define GD_CMD_PIOREAD 16 /* readcd */
|
nkeynes@1100 | 34 | #define GD_CMD_DMAREAD 17 /* readcd */
|
nkeynes@87 | 35 | #define GD_CMD_GETTOC 18
|
nkeynes@1100 | 36 | #define GD_CMD_GETTOC2 19 /* toc2 */
|
nkeynes@1100 | 37 | #define GD_CMD_PLAY 20 /* playcd */
|
nkeynes@1100 | 38 | #define GD_CMD_PLAY2 21 /* playcd */
|
nkeynes@1100 | 39 | #define GD_CMD_PAUSE 22 /* No params */
|
nkeynes@1100 | 40 | #define GD_CMD_RELEASE 23 /* No params */
|
nkeynes@1100 | 41 | #define GD_CMD_INIT 24 /* No params */
|
nkeynes@87 | 42 | #define GD_CMD_SEEK 27
|
nkeynes@87 | 43 | #define GD_CMD_READ 28
|
nkeynes@1100 | 44 | #define GD_CMD_STOP 33 /* No params */
|
nkeynes@87 | 45 | #define GD_CMD_GETSCD 34
|
nkeynes@87 | 46 | #define GD_CMD_GETSES 35
|
nkeynes@87 | 47 |
|
nkeynes@1100 | 48 | #define GD_CMD_STATUS_NONE 0
|
nkeynes@87 | 49 | #define GD_CMD_STATUS_ACTIVE 1
|
nkeynes@1100 | 50 | #define GD_CMD_STATUS_DONE 2
|
nkeynes@1100 | 51 | #define GD_CMD_STATUS_ABORT 3
|
nkeynes@1100 | 52 | #define GD_CMD_STATUS_ERROR 4
|
nkeynes@87 | 53 |
|
nkeynes@87 | 54 | #define GD_ERROR_OK 0
|
nkeynes@87 | 55 | #define GD_ERROR_NO_DISC 2
|
nkeynes@87 | 56 | #define GD_ERROR_DISC_CHANGE 6
|
nkeynes@87 | 57 | #define GD_ERROR_SYSTEM 1
|
nkeynes@87 | 58 |
|
nkeynes@1100 | 59 | typedef union gdrom_cmd_params {
|
nkeynes@1100 | 60 | struct gdrom_toc2_params {
|
nkeynes@1100 | 61 | uint32_t session;
|
nkeynes@1100 | 62 | sh4addr_t buffer;
|
nkeynes@1100 | 63 | } toc2;
|
nkeynes@87 | 64 |
|
nkeynes@1100 | 65 | struct gdrom_readcd_params {
|
nkeynes@1100 | 66 | cdrom_lba_t sector;
|
nkeynes@1100 | 67 | cdrom_count_t count;
|
nkeynes@1100 | 68 | sh4addr_t buffer;
|
nkeynes@1100 | 69 | uint32_t unknown;
|
nkeynes@1100 | 70 | } readcd;
|
nkeynes@1100 | 71 |
|
nkeynes@1100 | 72 | struct gdrom_playcd_params {
|
nkeynes@1100 | 73 | cdrom_lba_t start;
|
nkeynes@1100 | 74 | cdrom_lba_t end;
|
nkeynes@1100 | 75 | uint32_t repeat;
|
nkeynes@1100 | 76 | } playcd;
|
nkeynes@1100 | 77 | } *gdrom_cmd_params_t;
|
nkeynes@1100 | 78 |
|
nkeynes@1100 | 79 |
|
nkeynes@1100 | 80 |
|
nkeynes@1100 | 81 |
|
nkeynes@1100 | 82 | typedef struct gdrom_queue_entry {
|
nkeynes@87 | 83 | int status;
|
nkeynes@87 | 84 | uint32_t cmd_code;
|
nkeynes@502 | 85 | sh4ptr_t data;
|
nkeynes@87 | 86 | uint32_t result[4];
|
nkeynes@1100 | 87 | } *gdrom_queue_entry_t;
|
nkeynes@87 | 88 |
|
nkeynes@1100 | 89 | static struct gdrom_queue_entry gdrom_cmd_queue[COMMAND_QUEUE_LENGTH];
|
nkeynes@87 | 90 |
|
nkeynes@87 | 91 | static struct bios_gdrom_status {
|
nkeynes@87 | 92 | uint32_t status;
|
nkeynes@87 | 93 | uint32_t disk_type;
|
nkeynes@87 | 94 | } bios_gdrom_status;
|
nkeynes@87 | 95 |
|
nkeynes@1100 | 96 | void bios_gdrom_run_command( gdrom_queue_entry_t cmd )
|
nkeynes@87 | 97 | {
|
nkeynes@87 | 98 | DEBUG( "BIOS GD command %d", cmd->cmd_code );
|
nkeynes@87 | 99 | switch( cmd->cmd_code ) {
|
nkeynes@87 | 100 | case GD_CMD_INIT:
|
nkeynes@736 | 101 | /* *shrug* */
|
nkeynes@736 | 102 | cmd->status = GD_CMD_STATUS_DONE;
|
nkeynes@736 | 103 | break;
|
nkeynes@87 | 104 | default:
|
nkeynes@736 | 105 | cmd->status = GD_CMD_STATUS_ERROR;
|
nkeynes@736 | 106 | cmd->result[0] = GD_ERROR_SYSTEM;
|
nkeynes@736 | 107 | break;
|
nkeynes@87 | 108 | }
|
nkeynes@87 | 109 | }
|
nkeynes@87 | 110 |
|
nkeynes@87 | 111 | void bios_gdrom_init( void )
|
nkeynes@87 | 112 | {
|
nkeynes@87 | 113 | memset( &gdrom_cmd_queue, 0, sizeof(gdrom_cmd_queue) );
|
nkeynes@87 | 114 | }
|
nkeynes@87 | 115 |
|
nkeynes@502 | 116 | uint32_t bios_gdrom_enqueue( uint32_t cmd, sh4ptr_t ptr )
|
nkeynes@87 | 117 | {
|
nkeynes@87 | 118 | int i;
|
nkeynes@87 | 119 | for( i=0; i<COMMAND_QUEUE_LENGTH; i++ ) {
|
nkeynes@736 | 120 | if( gdrom_cmd_queue[i].status != GD_CMD_STATUS_ACTIVE ) {
|
nkeynes@736 | 121 | gdrom_cmd_queue[i].status = GD_CMD_STATUS_ACTIVE;
|
nkeynes@736 | 122 | gdrom_cmd_queue[i].cmd_code = cmd;
|
nkeynes@736 | 123 | gdrom_cmd_queue[i].data = ptr;
|
nkeynes@736 | 124 | return i;
|
nkeynes@736 | 125 | }
|
nkeynes@87 | 126 | }
|
nkeynes@87 | 127 | return -1;
|
nkeynes@87 | 128 | }
|
nkeynes@87 | 129 |
|
nkeynes@87 | 130 | void bios_gdrom_run_queue( void )
|
nkeynes@87 | 131 | {
|
nkeynes@87 | 132 | int i;
|
nkeynes@87 | 133 | for( i=0; i<COMMAND_QUEUE_LENGTH; i++ ) {
|
nkeynes@736 | 134 | if( gdrom_cmd_queue[i].status == GD_CMD_STATUS_ACTIVE ) {
|
nkeynes@736 | 135 | bios_gdrom_run_command( &gdrom_cmd_queue[i] );
|
nkeynes@736 | 136 | }
|
nkeynes@87 | 137 | }
|
nkeynes@87 | 138 | }
|
nkeynes@87 | 139 |
|
nkeynes@1100 | 140 | gdrom_queue_entry_t bios_gdrom_get_command( uint32_t id )
|
nkeynes@87 | 141 | {
|
nkeynes@87 | 142 | if( id >= COMMAND_QUEUE_LENGTH ||
|
nkeynes@736 | 143 | gdrom_cmd_queue[id].status == GD_CMD_STATUS_NONE )
|
nkeynes@736 | 144 | return NULL;
|
nkeynes@87 | 145 | return &gdrom_cmd_queue[id];
|
nkeynes@87 | 146 | }
|
nkeynes@87 | 147 |
|
nkeynes@87 | 148 | /**
|
nkeynes@87 | 149 | * Syscall list courtesy of Marcus Comstedt
|
nkeynes@87 | 150 | */
|
nkeynes@87 | 151 |
|
nkeynes@87 | 152 | void bios_syscall( uint32_t syscallid )
|
nkeynes@87 | 153 | {
|
nkeynes@1100 | 154 | gdrom_queue_entry_t cmd;
|
nkeynes@87 | 155 |
|
nkeynes@87 | 156 | switch( syscallid ) {
|
nkeynes@87 | 157 | case 0xB0: /* sysinfo */
|
nkeynes@736 | 158 | break;
|
nkeynes@87 | 159 | case 0xB4: /* Font */
|
nkeynes@736 | 160 | break;
|
nkeynes@87 | 161 | case 0xB8: /* Flash */
|
nkeynes@736 | 162 | break;
|
nkeynes@87 | 163 | case 0xBC: /* Misc/GD-Rom */
|
nkeynes@736 | 164 | switch( sh4r.r[6] ) {
|
nkeynes@736 | 165 | case 0: /* GD-Rom */
|
nkeynes@736 | 166 | switch( sh4r.r[7] ) {
|
nkeynes@736 | 167 | case 0: /* Send command */
|
nkeynes@736 | 168 | if( sh4r.r[5] == 0 )
|
nkeynes@736 | 169 | sh4r.r[0] = bios_gdrom_enqueue( sh4r.r[4], NULL );
|
nkeynes@736 | 170 | else
|
nkeynes@736 | 171 | sh4r.r[0] = bios_gdrom_enqueue( sh4r.r[4], mem_get_region(sh4r.r[5]) );
|
nkeynes@736 | 172 | break;
|
nkeynes@736 | 173 | case 1: /* Check command */
|
nkeynes@736 | 174 | cmd = bios_gdrom_get_command( sh4r.r[4] );
|
nkeynes@736 | 175 | if( cmd == NULL ) {
|
nkeynes@736 | 176 | sh4r.r[0] = GD_CMD_STATUS_NONE;
|
nkeynes@736 | 177 | } else {
|
nkeynes@736 | 178 | sh4r.r[0] = cmd->status;
|
nkeynes@736 | 179 | if( cmd->status == GD_CMD_STATUS_ERROR &&
|
nkeynes@736 | 180 | sh4r.r[5] != 0 ) {
|
nkeynes@736 | 181 | mem_copy_to_sh4( sh4r.r[5], (sh4ptr_t)&cmd->result, sizeof(cmd->result) );
|
nkeynes@736 | 182 | }
|
nkeynes@736 | 183 | }
|
nkeynes@736 | 184 | break;
|
nkeynes@736 | 185 | case 2: /* Mainloop */
|
nkeynes@736 | 186 | bios_gdrom_run_queue();
|
nkeynes@736 | 187 | break;
|
nkeynes@736 | 188 | case 3: /* Init */
|
nkeynes@736 | 189 | bios_gdrom_init();
|
nkeynes@736 | 190 | break;
|
nkeynes@736 | 191 | case 4: /* Drive status */
|
nkeynes@736 | 192 | if( sh4r.r[4] != 0 ) {
|
nkeynes@736 | 193 | mem_copy_to_sh4( sh4r.r[4], (sh4ptr_t)&bios_gdrom_status,
|
nkeynes@736 | 194 | sizeof(bios_gdrom_status) );
|
nkeynes@736 | 195 | }
|
nkeynes@736 | 196 | sh4r.r[0] = 0;
|
nkeynes@736 | 197 | break;
|
nkeynes@736 | 198 | case 8: /* Abort command */
|
nkeynes@736 | 199 | cmd = bios_gdrom_get_command( sh4r.r[4] );
|
nkeynes@736 | 200 | if( cmd == NULL || cmd->status != GD_CMD_STATUS_ACTIVE ) {
|
nkeynes@736 | 201 | sh4r.r[0] = -1;
|
nkeynes@736 | 202 | } else {
|
nkeynes@736 | 203 | cmd->status = GD_CMD_STATUS_ABORT;
|
nkeynes@736 | 204 | sh4r.r[0] = 0;
|
nkeynes@736 | 205 | }
|
nkeynes@736 | 206 | break;
|
nkeynes@736 | 207 | case 9: /* Reset */
|
nkeynes@736 | 208 | break;
|
nkeynes@736 | 209 | case 10: /* Set mode */
|
nkeynes@736 | 210 | sh4r.r[0] = 0;
|
nkeynes@736 | 211 | break;
|
nkeynes@736 | 212 | }
|
nkeynes@736 | 213 | break;
|
nkeynes@736 | 214 | case -1: /* Misc */
|
nkeynes@736 | 215 | break;
|
nkeynes@736 | 216 | default: /* ??? */
|
nkeynes@736 | 217 | break;
|
nkeynes@736 | 218 | }
|
nkeynes@736 | 219 | break;
|
nkeynes@736 | 220 | case 0xE0: /* Menu */
|
nkeynes@736 | 221 | switch( sh4r.r[7] ) {
|
nkeynes@736 | 222 | case 0:
|
nkeynes@736 | 223 | WARN( "Entering main program" );
|
nkeynes@736 | 224 | break;
|
nkeynes@736 | 225 | case 1:
|
nkeynes@736 | 226 | WARN( "Program aborted to DC menu");
|
nkeynes@736 | 227 | dreamcast_stop();
|
nkeynes@736 | 228 | break;
|
nkeynes@736 | 229 | }
|
nkeynes@87 | 230 | }
|
nkeynes@87 | 231 | }
|
nkeynes@102 | 232 |
|
nkeynes@1099 | 233 | void bios_boot( uint32_t syscallid )
|
nkeynes@1099 | 234 | {
|
nkeynes@1099 | 235 | /* Initialize hardware */
|
nkeynes@1099 | 236 | /* Boot disc if present */
|
nkeynes@1100 | 237 | if( bios_boot_gdrom_disc() ) {
|
nkeynes@1100 | 238 | sh4r.pr = sh4r.pc; /* Set the syscall return address to the bootstrap entry */
|
nkeynes@1100 | 239 | } else {
|
nkeynes@1100 | 240 | dreamcast_stop();
|
nkeynes@1100 | 241 | }
|
nkeynes@1099 | 242 | }
|
nkeynes@1099 | 243 |
|
nkeynes@102 | 244 | void bios_install( void )
|
nkeynes@102 | 245 | {
|
nkeynes@102 | 246 | bios_gdrom_init();
|
nkeynes@102 | 247 | syscall_add_hook_vector( 0xB0, 0x8C0000B0, bios_syscall );
|
nkeynes@102 | 248 | syscall_add_hook_vector( 0xB4, 0x8C0000B4, bios_syscall );
|
nkeynes@102 | 249 | syscall_add_hook_vector( 0xB8, 0x8C0000B8, bios_syscall );
|
nkeynes@102 | 250 | syscall_add_hook_vector( 0xBC, 0x8C0000BC, bios_syscall );
|
nkeynes@102 | 251 | syscall_add_hook_vector( 0xE0, 0x8C0000E0, bios_syscall );
|
nkeynes@102 | 252 | }
|
nkeynes@1099 | 253 |
|
nkeynes@1099 | 254 | #define MIN_ISO_SECTORS 32
|
nkeynes@1099 | 255 |
|
nkeynes@1099 | 256 | gboolean bios_boot_gdrom_disc( void )
|
nkeynes@1099 | 257 | {
|
nkeynes@1099 | 258 | cdrom_disc_t disc = gdrom_get_current_disc();
|
nkeynes@1099 | 259 |
|
nkeynes@1099 | 260 | int status = gdrom_get_drive_status();
|
nkeynes@1099 | 261 | if( status == CDROM_DISC_NONE ) {
|
nkeynes@1099 | 262 | ERROR( "No disc in drive" );
|
nkeynes@1099 | 263 | return FALSE;
|
nkeynes@1099 | 264 | }
|
nkeynes@1099 | 265 |
|
nkeynes@1099 | 266 | /* Find the bootable data track (if present) */
|
nkeynes@1099 | 267 | cdrom_track_t track = gdrom_disc_get_boot_track(disc);
|
nkeynes@1099 | 268 | if( track == NULL ) {
|
nkeynes@1099 | 269 | ERROR( "Disc is not bootable" );
|
nkeynes@1099 | 270 | return FALSE;
|
nkeynes@1099 | 271 | }
|
nkeynes@1099 | 272 | uint32_t lba = track->lba;
|
nkeynes@1099 | 273 | uint32_t sectors = cdrom_disc_get_track_size(disc,track);
|
nkeynes@1099 | 274 | if( sectors < MIN_ISO_SECTORS ) {
|
nkeynes@1099 | 275 | ERROR( "Disc is not bootable" );
|
nkeynes@1099 | 276 | return FALSE;
|
nkeynes@1099 | 277 | }
|
nkeynes@1099 | 278 | /* Load the initial bootstrap into DC ram at 8c008000 */
|
nkeynes@1099 | 279 | size_t length = BOOTSTRAP_SIZE;
|
nkeynes@1099 | 280 | unsigned char *bootstrap = mem_get_region(BOOTSTRAP_LOAD_ADDR);
|
nkeynes@1099 | 281 | if( cdrom_disc_read_sectors( disc, track->lba, BOOTSTRAP_SIZE/2048,
|
nkeynes@1099 | 282 | CDROM_READ_DATA|CDROM_READ_MODE2_FORM1, bootstrap, &length ) !=
|
nkeynes@1099 | 283 | CDROM_ERROR_OK ) {
|
nkeynes@1099 | 284 | ERROR( "Disc is not bootable" );
|
nkeynes@1099 | 285 | return FALSE;
|
nkeynes@1099 | 286 | }
|
nkeynes@1099 | 287 |
|
nkeynes@1099 | 288 | /* Check the magic just to be sure */
|
nkeynes@1099 | 289 | dc_bootstrap_head_t metadata = (dc_bootstrap_head_t)bootstrap;
|
nkeynes@1099 | 290 | if( memcmp( metadata->magic, BOOTSTRAP_MAGIC, BOOTSTRAP_MAGIC_SIZE ) != 0 ) {
|
nkeynes@1099 | 291 | ERROR( "Disc is not bootable (missing dreamcast bootstrap)" );
|
nkeynes@1099 | 292 | return FALSE;
|
nkeynes@1099 | 293 | }
|
nkeynes@1099 | 294 |
|
nkeynes@1099 | 295 | /* Get the initial program from the bootstrap (usually 1ST_READ.BIN) */
|
nkeynes@1099 | 296 | char program_name[17];
|
nkeynes@1099 | 297 | memcpy(program_name, metadata->boot_file, 16);
|
nkeynes@1099 | 298 | program_name[16] = '\0';
|
nkeynes@1099 | 299 | for( int i=15; i >= 0 && program_name[i] == ' '; i-- ) {
|
nkeynes@1099 | 300 | program_name[i] = '\0';
|
nkeynes@1099 | 301 | }
|
nkeynes@1099 | 302 |
|
nkeynes@1099 | 303 | /* Bootstrap is good. Now find the program in the actual filesystem... */
|
nkeynes@1099 | 304 | isofs_reader_t iso = isofs_reader_new_from_track( disc, track, NULL );
|
nkeynes@1099 | 305 | if( iso == NULL ) {
|
nkeynes@1099 | 306 | ERROR( "Disc is not bootable" );
|
nkeynes@1099 | 307 | return FALSE;
|
nkeynes@1099 | 308 | }
|
nkeynes@1099 | 309 | isofs_reader_dirent_t ent = isofs_reader_get_file( iso, program_name );
|
nkeynes@1099 | 310 | if( ent == NULL ) {
|
nkeynes@1099 | 311 | ERROR( "Disc is not bootable (initial program '%s' not found)", program_name );
|
nkeynes@1099 | 312 | isofs_reader_destroy(iso);
|
nkeynes@1099 | 313 | return FALSE;
|
nkeynes@1099 | 314 | }
|
nkeynes@1099 | 315 |
|
nkeynes@1099 | 316 | if( ent->size > (0x8D000000 - BINARY_LOAD_ADDR) ) {
|
nkeynes@1099 | 317 | /* Bootstrap isn't going to fit in memory. Complain and abort */
|
nkeynes@1099 | 318 | ERROR( "Disc is not bootable (initial program too large)" );
|
nkeynes@1099 | 319 | isofs_reader_destroy(iso);
|
nkeynes@1099 | 320 | return FALSE;
|
nkeynes@1099 | 321 | }
|
nkeynes@1099 | 322 | unsigned char *program = mem_get_region(BINARY_LOAD_ADDR);
|
nkeynes@1099 | 323 | int program_sectors = (ent->size+2047)/2048;
|
nkeynes@1099 | 324 | if( disc->disc_type == CDROM_DISC_GDROM ) {
|
nkeynes@1099 | 325 | /* Load the binary directly into RAM */
|
nkeynes@1099 | 326 | if( isofs_reader_read_file( iso, ent, 0, ent->size, program ) !=
|
nkeynes@1099 | 327 | CDROM_ERROR_OK ) {
|
nkeynes@1099 | 328 | ERROR( "Disc is not bootable (failed to read initial program)\n" );
|
nkeynes@1099 | 329 | isofs_reader_destroy(iso);
|
nkeynes@1099 | 330 | return FALSE;
|
nkeynes@1099 | 331 | }
|
nkeynes@1100 | 332 | asic_enable_ide_interface(TRUE);
|
nkeynes@1099 | 333 | } else {
|
nkeynes@1099 | 334 | /* Load the binary into a temp buffer */
|
nkeynes@1099 | 335 | unsigned char tmp[program_sectors*2048];
|
nkeynes@1099 | 336 | if( isofs_reader_read_file( iso, ent, 0, ent->size, tmp ) !=
|
nkeynes@1099 | 337 | CDROM_ERROR_OK ) {
|
nkeynes@1099 | 338 | ERROR( "Disc is not bootable (failed to read initial program)\n" );
|
nkeynes@1099 | 339 | isofs_reader_destroy(iso);
|
nkeynes@1099 | 340 | return FALSE;
|
nkeynes@1099 | 341 | }
|
nkeynes@1099 | 342 | bootprogram_unscramble(program, tmp, ent->size);
|
nkeynes@1100 | 343 | asic_enable_ide_interface(FALSE);
|
nkeynes@1099 | 344 | }
|
nkeynes@1099 | 345 | isofs_reader_destroy(iso);
|
nkeynes@1100 | 346 | dreamcast_program_loaded( "", BOOTSTRAP_ENTRY_ADDR );
|
nkeynes@1100 | 347 | return TRUE;
|
nkeynes@1099 | 348 | }
|