1241 lines
32 KiB
C
1241 lines
32 KiB
C
/*
|
|
* Copyright (c) 2013, Peter Rutenbar <pruten@gmail.com>
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
|
* list of conditions and the following disclaimer.
|
|
* 2. 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.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
#include <GLUT/glut.h>
|
|
#include <pthread.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include "../shoebill.h"
|
|
#include "../coff.h"
|
|
|
|
struct dbg_state_t dbg_state;
|
|
|
|
void print_mmu_rp(uint64_t rp)
|
|
{
|
|
printf("lu=%u limit=0x%x sg=%u dt=%u addr=0x%08x\n", rp_lu(rp), rp_limit(rp), rp_sg(rp), rp_dt(rp), rp_addr(rp));
|
|
}
|
|
|
|
void printregs()
|
|
{
|
|
printf("[d0]%08x [d1]%08x [d2]%08x [d3]%08x\n", shoe.d[0], shoe.d[1], shoe.d[2], shoe.d[3]);
|
|
printf("[d4]%08x [d5]%08x [d6]%08x [d7]%08x\n", shoe.d[4], shoe.d[5], shoe.d[6], shoe.d[7]);
|
|
printf("[a0]%08x [a1]%08x [a2]%08x [a3]%08x\n", shoe.a[0], shoe.a[1], shoe.a[2], shoe.a[3]);
|
|
printf("[a4]%08x [a5]%08x [a6]%08x [a7]%08x\n", shoe.a[4], shoe.a[5], shoe.a[6], shoe.a[7]);
|
|
printf("[pc]%08x [sr]%c%c%c%c%c%c%c [tc]%08x\n", shoe.pc,
|
|
sr_s()?'S':'s',
|
|
sr_m()?'M':'m',
|
|
sr_x()?'X':'x',
|
|
sr_n()?'N':'n',
|
|
sr_z()?'Z':'z',
|
|
sr_v()?'V':'v',
|
|
sr_c()?'C':'c',
|
|
shoe.tc
|
|
);
|
|
|
|
printf("[vbr]%08x\n", shoe.vbr);
|
|
|
|
printf("srp: ");
|
|
print_mmu_rp(shoe.srp);
|
|
|
|
printf("crp: ");
|
|
print_mmu_rp(shoe.crp);
|
|
|
|
printf("tc: e=%u sre=%u fcl=%u ps=%u is=%u (tia=%u tib=%u tic=%u tid=%u)\n",
|
|
tc_enable(), tc_sre(), tc_fcl(), tc_ps(), tc_is(), tc_tia(), tc_tib(), tc_tic(), tc_tid());
|
|
|
|
printf("\n");
|
|
}
|
|
|
|
void print_pc()
|
|
{
|
|
char str[1024];
|
|
uint8_t binary[32];
|
|
uint32_t i;
|
|
uint32_t len;
|
|
const char *name = NULL;
|
|
|
|
if ((shoe.pc >= 0x40000000) && (shoe.pc < 0x50000000)) {
|
|
uint32_t i, addr = shoe.pc % (shoe.physical_rom_size);
|
|
for (i=0; macii_rom_symbols[i].name; i++) {
|
|
if (macii_rom_symbols[i].addr > addr) {
|
|
break;
|
|
}
|
|
name = macii_rom_symbols[i].name;
|
|
}
|
|
}
|
|
else if (sr_s()) { // these symbols are only meaningful in supervisor mode
|
|
coff_symbol *symb = coff_find_func(shoe.coff, shoe.pc);
|
|
if (symb && strlen(symb->name))
|
|
name = symb->name;
|
|
}
|
|
else {
|
|
if ((shoe.pc >= 0x10000000) && (shoe.pc < 0x20000000)) {
|
|
uint32_t i, addr = shoe.pc % (shoe.physical_rom_size);
|
|
for (i=0; macii_rom_symbols[i].name; i++) {
|
|
if (macii_rom_symbols[i].addr > addr) {
|
|
break;
|
|
}
|
|
name = macii_rom_symbols[i].name;
|
|
}
|
|
}
|
|
else {
|
|
coff_symbol *symb = coff_find_func(shoe.launch, shoe.pc);
|
|
if (symb)
|
|
name = symb->name;
|
|
}
|
|
}
|
|
|
|
const uint16_t old_abort = shoe.abort;
|
|
shoe.suppress_exceptions = 1;
|
|
|
|
for (i=0; i<32; i++) {
|
|
binary[i] = (uint8_t) lget(shoe.pc+i, 1);
|
|
}
|
|
|
|
disassemble_inst(binary, shoe.pc, str, &len);
|
|
|
|
printf("*0x%08x %s [ ", shoe.pc, name ? name : "");
|
|
for (i=0; i<len; i+=2) {
|
|
printf("%02x%02x ", binary[i], binary[i+1]);
|
|
}
|
|
printf("] %s\n", str);
|
|
|
|
shoe.abort = old_abort;
|
|
shoe.suppress_exceptions = 0;
|
|
|
|
}
|
|
|
|
static void dump_proc(uint32_t procnum)
|
|
{
|
|
uint32_t u_proc_p;
|
|
uint16_t pid;
|
|
uint8_t do_print = 0, cpuflag;
|
|
|
|
// Only dump this process state if we're in user mode
|
|
if (sr_s())
|
|
return ;
|
|
|
|
shoe.suppress_exceptions = 1;
|
|
cpuflag = lget(0x0000012f, 1);
|
|
set_sr_s(1); // set supervisor mode so we can access the proc structure
|
|
|
|
u_proc_p = lget(0x1ff01000, 4);
|
|
if (shoe.abort)
|
|
goto done;
|
|
|
|
pid = lget(u_proc_p + 0x26, 2);
|
|
if (shoe.abort)
|
|
goto done;
|
|
|
|
do_print = 1;
|
|
|
|
done:
|
|
|
|
set_sr_s(0);
|
|
shoe.abort = 0;
|
|
shoe.suppress_exceptions = 0;
|
|
|
|
if (do_print) {
|
|
printf("pid = %u, cpuflag=0x%02x\n", pid, cpuflag);
|
|
// print_pc();
|
|
// printregs();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
void stepper()
|
|
{
|
|
dbg_breakpoint_t *cur;
|
|
uint32_t i;
|
|
uint32_t len;
|
|
|
|
check_time(); // FIXME: move this to another thread
|
|
|
|
if (shoe.pending_interrupt) {
|
|
process_pending_interrupt();
|
|
shoe.stopped = 0;
|
|
}
|
|
else if (shoe.stopped) {
|
|
// usleep(1);
|
|
return ;
|
|
}
|
|
cpu_step();
|
|
|
|
for (cur = dbg_state.breakpoints; cur != NULL; cur = cur->next) {
|
|
if (shoe.pc == cur->addr) {
|
|
printf("Hit breakpoint %llu *0x%08x\n", cur->num, shoe.pc);
|
|
dbg_state.running = 0;
|
|
return ;
|
|
}
|
|
}
|
|
|
|
if (shoe.dbg) {
|
|
// printf("*0x1ff01074 = %llx\n", lget(0x1ff01074, 4));
|
|
print_pc();
|
|
printregs();
|
|
}
|
|
|
|
// if ((shoe.pc >= 0x40000000) && (shoe.pc < 0x50000000)) {
|
|
// //print_pc();
|
|
// //printregs();
|
|
// }
|
|
// else {
|
|
// coff_symbol *symb = coff_find_func(shoe.coff, shoe.pc);
|
|
// if ((symb && (strcmp(symb->name, "scsitask")==0)) ||
|
|
// /*(symb && (strcmp(symb->name, "scsiget")==0)) ||
|
|
// (symb && (strcmp(symb->name, "scsi_out")==0)) ||
|
|
// (symb && (strcmp(symb->name, "scsireq")==0)) ||
|
|
// (symb && (strcmp(symb->name, "scsi_vio")==0)) ||
|
|
// (symb && (strcmp(symb->name, "binit")==0)) ||*/
|
|
// (symb && (strcmp(symb->name, "realvtopte")==0)) ||
|
|
// (symb && (strcmp(symb->name, "realvtop")==0)) ||
|
|
// (symb && (strcmp(symb->name, "realsvtop")==0)) || /*
|
|
// (symb && (strcmp(symb->name, "sdcmd")==0)) ||
|
|
// (symb && (strcmp(symb->name, "sdread")==0)) ||
|
|
// (symb && (strcmp(symb->name, "gdpartinit")==0)) ||
|
|
// (symb && (strcmp(symb->name, "scsi_in")==0)) ||
|
|
// (symb && (strcmp(symb->name, "gddriveinit")==0)) ||
|
|
// (symb && (strcmp(symb->name, "vio_init")==0)) ||
|
|
// (symb && (strcmp(symb->name, "get_psr")==0)) ||
|
|
// (symb && (strcmp(symb->name, "scsisched")==0)) ||
|
|
// (symb && (strcmp(symb->name, "choosetask")==0)) || */
|
|
// (symb && (strcmp(symb->name, "scsiselect")==0))) {
|
|
// print_pc();
|
|
// printregs();
|
|
// }
|
|
// else if ((shoe.pc > (0x0014ea02-32)) && (shoe.pc < (0x0014ea02+32))) {
|
|
// print_pc();
|
|
// printregs();
|
|
// }
|
|
// }
|
|
}
|
|
|
|
|
|
char *cli_prompt_callback(EditLine *el)
|
|
{
|
|
return "~ ";
|
|
}
|
|
|
|
// Hack to clear line after ^C. el_reset() screws up tty when called from the signal handler.
|
|
void ch_reset(EditLine *el, int mclear);
|
|
|
|
void signal_callback(int sig)
|
|
{
|
|
EditLine *el = dbg_state.el;
|
|
(void) signal(SIGINT, signal_callback);
|
|
(void) signal(SIGWINCH, signal_callback);
|
|
|
|
switch (sig) {
|
|
case SIGWINCH:
|
|
el_resize(el);
|
|
break ;
|
|
case SIGINT:
|
|
if (dbg_state.running) {
|
|
dbg_state.running = 0;
|
|
}
|
|
else {
|
|
printf("\n");
|
|
ch_reset(el, 0);
|
|
el_set(el, EL_REFRESH);
|
|
}
|
|
break ;
|
|
}
|
|
|
|
return ;
|
|
}
|
|
|
|
void verb_backtrace_handler (const char *line)
|
|
{
|
|
const uint32_t old_abort = shoe.abort;
|
|
shoe.suppress_exceptions = 1;
|
|
shoe.abort = 0;
|
|
|
|
// link
|
|
// push a6 to a7
|
|
// set a6 = a7
|
|
// set a7 = a7 - (some stack space)
|
|
|
|
// jsr
|
|
// push return pointer to a7
|
|
|
|
// call
|
|
// set a7 = a7 - (some stack space)
|
|
// push arguments to a7
|
|
// push return pointer to a7
|
|
// (jump to function)
|
|
// push
|
|
|
|
// bt algorithm
|
|
// set a7 = a6
|
|
// pop a7 -> a6
|
|
// pop a7 -> return pointer
|
|
|
|
|
|
uint32_t i, j, a7, a6 = shoe.a[6];
|
|
coff_symbol *symb;
|
|
|
|
if (sr_s()) {
|
|
symb = coff_find_func(shoe.coff, shoe.pc);
|
|
printf("%u: *0x%08x %s+%u\n", 0, shoe.pc, (symb && strlen(symb->name))?symb->name:"?", shoe.pc - symb->value);
|
|
}
|
|
else
|
|
printf("%u: *0x%08x\n", 0, shoe.pc);
|
|
|
|
for (i=1; 1; i++) {
|
|
a7 = a6;
|
|
const uint32_t last_a6 = lget(a7, 4);
|
|
const uint32_t last_pc = lget(a7+4, 4);
|
|
|
|
if ((last_a6 - a6) <= 1000) {
|
|
printf(" {");
|
|
for (j = a6+8; j < last_a6; j+=4) {
|
|
uint32_t data = lget(j, 4);
|
|
printf("%x, ", data);
|
|
}
|
|
printf("}\n");
|
|
}
|
|
|
|
if (sr_s()) {
|
|
symb = coff_find_func(shoe.coff, last_pc);
|
|
printf("%u: *0x%08x %s+%u\n", i, last_pc, (symb && strlen(symb->name))?symb->name:"?", last_pc - symb->value);
|
|
}
|
|
else
|
|
printf("%u: *0x%08x\n", i, last_pc);
|
|
|
|
if ((last_a6 - a6) > 1000) {
|
|
break;
|
|
}
|
|
|
|
a6 = last_a6;
|
|
}
|
|
|
|
shoe.suppress_exceptions = 0;
|
|
shoe.abort = old_abort;
|
|
}
|
|
|
|
void verb_break_handler (const char *line)
|
|
{
|
|
errno = 0;
|
|
const uint32_t addr = (uint32_t) strtoul(line, NULL, 0);
|
|
|
|
if (errno) {
|
|
printf("errno: %d\n", errno);
|
|
return ;
|
|
}
|
|
|
|
dbg_breakpoint_t *brk = calloc(sizeof(dbg_breakpoint_t), 1);
|
|
brk->next = NULL;
|
|
brk->addr = addr;
|
|
brk->num = dbg_state.breakpoint_counter++;
|
|
|
|
dbg_breakpoint_t **cur = &dbg_state.breakpoints;
|
|
while (*cur)
|
|
cur = &(*cur)->next;
|
|
*cur = brk;
|
|
|
|
printf("Set breakpoint %llu = *0x%08x\n", brk->num, brk->addr);
|
|
}
|
|
|
|
void verb_delete_handler (const char *line)
|
|
{
|
|
errno = 0;
|
|
uint64_t num = strtoull(line, NULL, 0);
|
|
|
|
if (errno) {
|
|
printf("errno: %d\n", errno);
|
|
return ;
|
|
}
|
|
|
|
dbg_breakpoint_t **cur = &dbg_state.breakpoints;
|
|
while (*cur) {
|
|
if ((*cur)->num == num) {
|
|
dbg_breakpoint_t *victim = *cur;
|
|
*cur = (*cur)->next;
|
|
free(victim);
|
|
return ;
|
|
}
|
|
cur = &(*cur)->next;
|
|
}
|
|
|
|
printf("No such breakpoint (#%llu)\n", num);
|
|
}
|
|
|
|
void verb_help_handler (const char *line)
|
|
{
|
|
printf("Help help help\n");
|
|
}
|
|
|
|
void verb_quit_handler (const char *line)
|
|
{
|
|
printf("Quitting\n");
|
|
fflush(stdout);
|
|
exit(0);
|
|
}
|
|
|
|
void verb_continue_handler (const char *line)
|
|
{
|
|
dbg_state.running = 1;
|
|
while (dbg_state.running) {
|
|
stepper();
|
|
}
|
|
print_pc();
|
|
}
|
|
|
|
void verb_stepi_handler (const char *line)
|
|
{
|
|
shoe.stopped = 0;
|
|
dbg_state.running = 1;
|
|
cpu_step();
|
|
dbg_state.running = 0;
|
|
print_pc();
|
|
}
|
|
|
|
void verb_registers_handler (const char *line)
|
|
{
|
|
printregs();
|
|
}
|
|
|
|
void verb_trace_toggle_handler (const char *line)
|
|
{
|
|
shoe.dbg = !shoe.dbg;
|
|
}
|
|
|
|
void verb_examine_handler (const char *line)
|
|
{
|
|
uint32_t addr = (uint32_t)strtoul(line, NULL, 0);
|
|
uint32_t old_suppress = shoe.suppress_exceptions;
|
|
|
|
shoe.suppress_exceptions = 1;
|
|
printf("(uint32_t)*0x%08x = 0x%08x\n", addr, (uint32_t)lget(addr, 4));
|
|
shoe.suppress_exceptions = old_suppress;
|
|
|
|
}
|
|
|
|
void verb_lookup_handler (const char *line)
|
|
{
|
|
char *sym_name = malloc(strlen(line)+1);
|
|
|
|
sscanf(line, "%s", sym_name);
|
|
coff_symbol *symb = coff_find_symbol(shoe.coff, sym_name);
|
|
|
|
free(sym_name);
|
|
|
|
if (symb == NULL) {
|
|
printf("Couldn't find \"%s\"\n", sym_name);
|
|
return ;
|
|
}
|
|
|
|
printf("%s = *0x%08x\n", symb->name, symb->value);
|
|
}
|
|
|
|
struct verb_handler_table_t {
|
|
const char *name;
|
|
void (*func)(const char *);
|
|
} verb_handler_table[] =
|
|
{
|
|
{"quit", verb_quit_handler},
|
|
{"help", verb_help_handler},
|
|
{"registers", verb_registers_handler},
|
|
{"continue", verb_continue_handler},
|
|
{"stepi", verb_stepi_handler},
|
|
{"backtrace", verb_backtrace_handler},
|
|
{"bt", verb_backtrace_handler},
|
|
{"break", verb_break_handler},
|
|
{"delete", verb_delete_handler},
|
|
{"lookup", verb_lookup_handler},
|
|
{"trace", verb_trace_toggle_handler},
|
|
{"x", verb_examine_handler}
|
|
};
|
|
|
|
void execute_verb (const char *line)
|
|
{
|
|
char verb[128];
|
|
uint32_t max_len=0, max_i=0;
|
|
const char *remainder;
|
|
uint32_t i, matches = 0, match_i;
|
|
|
|
if (sscanf(line, "%127s", verb) != 1)
|
|
return ;
|
|
|
|
// Skip past the verb
|
|
for (remainder = line; *remainder && !isspace(*remainder); remainder++) ;
|
|
|
|
// Skip past the space between the verb and the arguments
|
|
for (; *remainder && isspace(*remainder); remainder++) ;
|
|
|
|
const uint32_t verb_len = strlen(verb);
|
|
for (i=0; i < (sizeof(verb_handler_table) / sizeof(struct verb_handler_table_t)); i++) {
|
|
const uint32_t i_len = strlen(verb_handler_table[i].name);
|
|
|
|
// If it's a perfect match,
|
|
if (strcasecmp(verb, verb_handler_table[i].name)==0) {
|
|
verb_handler_table[i].func(remainder);
|
|
return ;
|
|
}
|
|
|
|
// Otherwise, see if it's a partial match
|
|
if ((i_len >= verb_len) && strncasecmp(verb, verb_handler_table[i].name, verb_len)==0) {
|
|
matches++;
|
|
match_i = i;
|
|
}
|
|
}
|
|
|
|
// Only execute the verb if it's an unambiguous match (matches == 1)
|
|
if (matches == 1) {
|
|
verb_handler_table[match_i].func(remainder);
|
|
return ;
|
|
}
|
|
|
|
printf(" %s?\n", verb);
|
|
}
|
|
|
|
void *ui_thread (void *arg)
|
|
{
|
|
EditLine *el;
|
|
History *hist;
|
|
HistEvent histev;
|
|
|
|
const char *buf;
|
|
int num;
|
|
|
|
hist = history_init();
|
|
history(hist, &histev, H_SETSIZE, 10000); // Remember 10000 previous user inputs
|
|
|
|
el = el_init("Shoebill", stdin, stdout, stderr);
|
|
dbg_state.el = el;
|
|
|
|
el_set(el, EL_SIGNAL, 0);
|
|
el_set(el, EL_PROMPT, cli_prompt_callback);
|
|
el_set(el, EL_EDITOR, "emacs");
|
|
el_set(el, EL_HIST, history, hist);
|
|
|
|
(void) signal(SIGINT, signal_callback);
|
|
(void) signal(SIGWINCH, signal_callback);
|
|
|
|
while ((buf = el_gets(el, &num)) != NULL) {
|
|
if (strcmp(buf, "\n")!=0) {
|
|
execute_verb(buf);
|
|
history(hist, &histev, H_ENTER, buf);
|
|
}
|
|
}
|
|
|
|
el_end(el);
|
|
history_end(hist);
|
|
return NULL;
|
|
}
|
|
|
|
uint8_t *loadrom (char *path, uint32_t *len)
|
|
{
|
|
FILE *f = fopen(path, "r");
|
|
uint8_t *buf = calloc(1024, 1024);
|
|
uint32_t i, sz, expected, checksum;
|
|
|
|
if (!f) {
|
|
printf("Couldn't open %s\n", path);
|
|
return NULL;
|
|
}
|
|
|
|
for (sz=0; sz < (1024*1024); sz += (64*1024)) {
|
|
printf("sz = %u\n", sz);
|
|
if (fread(buf + sz, 64*1024, 1, f) != 1) {
|
|
break;
|
|
}
|
|
}
|
|
printf("sz = %u (done)\n", sz);
|
|
|
|
fclose(f);
|
|
|
|
if (sz == 0) {
|
|
printf("loadrom: empty rom file\n");
|
|
free(buf);
|
|
return NULL;
|
|
}
|
|
|
|
for (i=0, expected=0; i<4; i++)
|
|
expected = (expected << 8) + buf[i];
|
|
|
|
for (i=4, checksum=0; i < sz; i+=2) {
|
|
uint16_t word = (buf[i]<<8) + buf[i+1];
|
|
checksum += word;
|
|
}
|
|
|
|
if (checksum != expected) {
|
|
printf("Bad checksum (computed %08x, expected %08x)\n", checksum, expected);
|
|
free(buf);
|
|
return NULL;
|
|
}
|
|
|
|
*len = sz;
|
|
return buf;
|
|
}
|
|
|
|
struct __attribute__ ((__packed__)) kernel_info {
|
|
// Auto data
|
|
uint32_t auto_magic;
|
|
uint32_t auto_id[16];
|
|
uint32_t auto_version[16];
|
|
uint32_t auto_command;
|
|
|
|
uint16_t root_ctrl;
|
|
uint8_t root_drive;
|
|
uint8_t root_cluster;
|
|
|
|
struct __attribute__ ((__packed__)) sect_info {
|
|
uint32_t vstart;
|
|
uint32_t pstart;
|
|
uint32_t size;
|
|
} si[3];
|
|
|
|
uint16_t machine_type; // Gestalt, I think? The "machine_type" for a quadra 950 doesn't match its gestalt, though.
|
|
uint32_t drive_queue_offset;
|
|
uint16_t ki_flags;
|
|
uint8_t ki_version; // always 1
|
|
uint8_t root_partition;
|
|
uint16_t swap_ctrl;
|
|
uint8_t swap_drive;
|
|
uint8_t swap_partition;
|
|
};
|
|
|
|
void init_kernel_info()
|
|
{
|
|
struct kernel_info ki, *p;
|
|
uint32_t i;
|
|
|
|
p = (struct kernel_info*)0x00003c00;
|
|
|
|
shoe.d[0] = 0x536d7201;
|
|
shoe.a[0] = (uint32_t)p;
|
|
|
|
/* ----- Setup kernel info structure ----- */
|
|
|
|
ki.auto_magic = 0x50696773; // 'Pigs' (Pigs in space?)
|
|
|
|
for (i=0; i<16; i++) {
|
|
ki.auto_id[i] = 0x0000ffff;
|
|
ki.auto_version[i] = 0;
|
|
}
|
|
|
|
ki.auto_id[0xa] = 0x50; // Macintosh II video card has an auto_id of 5 (I guess?)
|
|
ki.auto_id[0xb] = 0x5; // Macintosh II video card has an auto_id of 5 (I guess?)
|
|
|
|
ki.auto_command = 0; // AUTO_RUN
|
|
|
|
ki.root_ctrl = 0;
|
|
ki.root_drive = 0;
|
|
ki.root_cluster = 0;
|
|
|
|
for (i = 0; i < shoe.coff->num_sections; i++) {
|
|
coff_section *s = &shoe.coff->sections[i];
|
|
uint8_t sect;
|
|
|
|
if (strcmp(s->name, ".text") == 0)
|
|
sect = 0;
|
|
else if (strcmp(s->name, ".data") == 0)
|
|
sect = 1;
|
|
else if (strcmp(s->name, ".bss") == 0)
|
|
sect = 2;
|
|
else
|
|
continue;
|
|
|
|
ki.si[sect].vstart = s->v_addr;
|
|
ki.si[sect].pstart = s->p_addr;
|
|
ki.si[sect].size = s->sz;
|
|
}
|
|
|
|
ki.machine_type = 4; // Macintosh II?
|
|
|
|
// +4 because the DrvQEl structure has a hidden "flags" field 4 bytes below the pointer
|
|
ki.drive_queue_offset = sizeof(struct kernel_info) + 4;
|
|
|
|
ki.ki_flags = 1; // KI_VERBOSE
|
|
ki.ki_version = 1;
|
|
ki.root_partition = 0;
|
|
ki.swap_ctrl = 0;
|
|
ki.swap_drive = 0;
|
|
ki.swap_partition = 1;
|
|
|
|
/* ----- Copy ki into memory ----- */
|
|
#define ki_pset(_f, _s) {pset((uint32_t)&p->_f, _s, ki._f); printf("Setting 0x%08x to %x\n", (uint32_t)&p->_f, ki._f);}
|
|
ki_pset(auto_magic, 4);
|
|
for (i=0; i<16; i++) {
|
|
ki_pset(auto_id[i], 4);
|
|
ki_pset(auto_version[i], 4);
|
|
}
|
|
ki_pset(auto_command, 4);
|
|
|
|
ki_pset(root_ctrl, 2);
|
|
ki_pset(root_drive, 1);
|
|
ki_pset(root_cluster, 1);
|
|
|
|
for (i=0; i<3; i++) {
|
|
ki_pset(si[i].vstart, 4);
|
|
ki_pset(si[i].pstart, 4);
|
|
ki_pset(si[i].size, 4);
|
|
}
|
|
|
|
ki_pset(machine_type, 2);
|
|
ki_pset(drive_queue_offset, 4);
|
|
ki_pset(ki_flags, 2);
|
|
ki_pset(ki_version, 1);
|
|
ki_pset(root_partition, 1);
|
|
ki_pset(swap_ctrl, 2);
|
|
ki_pset(swap_drive, 1);
|
|
ki_pset(swap_partition, 1);
|
|
|
|
// Fake drive queue
|
|
/*uint8_t dummy[0x14*2] = {
|
|
// 00 08 01 00 00 00 00 14 00 01 00 08 FF D9 00 00 CE AD 00 2C
|
|
0x00, 0x08, 0x01, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x01, 0x00, 0x08, 0xFF, 0xD9, 0x00, 0x00, 0x0f, 0x71, 0x00, 0x01,
|
|
0x00, 0x08, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x09, 0xFF, 0xDF, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00
|
|
};*/
|
|
|
|
uint8_t dummy[0x14*2] = {
|
|
// 00 08 01 00 00 00 00 14 00 01 00 08 FF D9 00 00 CE AD 00 2C
|
|
0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x01, 0x00, 0x08, 0xFF, 0xD9, 0x00, 0x00, 0, 0, 0, 0,
|
|
0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x09, 0xFF, 0xDF, 0x00, 0x00, 0, 0, 0, 0
|
|
};
|
|
|
|
for (i=0; i<(0x14*2); i++)
|
|
pset(((uint32_t)p) + sizeof(struct kernel_info) + i, 1, dummy[i]);
|
|
|
|
// FIXME: *really* implement drive queue
|
|
}
|
|
|
|
void init_macintosh_globals()
|
|
{
|
|
uint8_t buf[0x1000];
|
|
|
|
memset(buf, 0xBB, 0x1000);
|
|
|
|
uint32_t i;
|
|
for (i=0; i<0x1000; i++)
|
|
pset(i, 1, buf[i]);
|
|
|
|
|
|
#define hwCbSCSI (1<<15)
|
|
#define hwCbClock (1<<14)
|
|
#define hwCbExPRAM (1<<13)
|
|
#define hwCbFPU (1<<12)
|
|
#define hwCbMMU (1<<11)
|
|
#define hwCbADB (1<<10)
|
|
#define hwCbAUX (1<<9)
|
|
// uint16_t HWCfgFlags = hwCbSCSI | hwCbClock | hwCbFPU | hwCbMMU | hwCbADB | hwCbAUX;
|
|
|
|
uint16_t HWCfgFlags = hwCbSCSI | hwCbClock | hwCbFPU | hwCbMMU | hwCbADB;
|
|
|
|
pset(0xb22, 2, HWCfgFlags); // HWCfgFlags
|
|
|
|
pset(0x12f, 1, 0x02); // CPUFlag = 0x02 (MC68020)
|
|
pset(0x31a, 4, 0x00ffffff); // Lo3Bytes (always 0x00ffffff)
|
|
|
|
pset(0x28e, 2, 0x3fff); // ROM85 (always 0x3fff, I think?)
|
|
|
|
|
|
pset(0xdd8, 4, 0); // universal info ptr. is allowed to be null on Mac II, (I THINK)
|
|
pset(0x1d4, 4, 0x50000000); // VIA (via1 ptr)
|
|
pset(0x1d8, 4, 0x50004000); // SCC
|
|
|
|
/*pset(0x8ce, 1, 1); // CrsrNew (set to 1?)
|
|
pset(0x8cd, 1, 0); // CrsrBusy (0 -> Not presently busy)
|
|
pset(0x8d0, 2, 0); // CrsrState
|
|
pset(0x8cf, 1, 0xff); // CrsrCouple ??
|
|
pset(0x8d6, 4, 0xffffffff); // MouseMask
|
|
pset(0x8da, 4, 0); // MouseOffset
|
|
pset(0x8d3, 1, 6); // CrsrScale
|
|
*/
|
|
|
|
// 21e -> KbdType
|
|
// dd8 -> UnivInfoPtr
|
|
// d00 -> TimeDBRA
|
|
// d02 -> TimeSCCDB
|
|
// FIXME: add 0x21e, 0xdd8, 0x28e, 0xd00, 0xd02
|
|
|
|
// Do this after setting lomem mac stuff, because this structure is more important.
|
|
init_kernel_info();
|
|
}
|
|
|
|
uint32_t init_state (coff_file *coff, uint8_t *rom, uint32_t romsize)
|
|
{
|
|
uint32_t i, j, pc = 0xffffffff;
|
|
|
|
fpu_setup_jump_table();
|
|
|
|
shoe.coff = coff;
|
|
shoe.launch = coff_parser("priv/launch");
|
|
|
|
shoe.physical_mem_size = 32 * 1024 * 1024;
|
|
shoe.physical_rom_size = romsize;
|
|
|
|
shoe.physical_mem_base = valloc(shoe.physical_mem_size);
|
|
shoe.physical_rom_base = valloc(romsize);
|
|
|
|
memset(shoe.physical_mem_base, 0, shoe.physical_mem_size);
|
|
memcpy(shoe.physical_rom_base, rom, romsize);
|
|
|
|
for (i=0; i<16; i++) {
|
|
shoe.slots[i].slotnum = i;
|
|
shoe.slots[i].connected = 0;
|
|
shoe.slots[i].glut_window_id = -1;
|
|
}
|
|
|
|
// Install TFB at slot B
|
|
/*{
|
|
shoe.slots[0xb].connected = 1;
|
|
shoe.slots[0xb].read_func = nubus_tfb_read_func;
|
|
shoe.slots[0xb].write_func = nubus_tfb_write_func;
|
|
nubus_tfb_init(0xb);
|
|
}*/
|
|
|
|
{
|
|
shoe.slots[0xa].connected = 1;
|
|
shoe.slots[0xa].read_func = nubus_video_read_func;
|
|
shoe.slots[0xa].write_func = nubus_video_write_func;
|
|
// nubus_video_init(0xa, 1440, 900);
|
|
nubus_video_init(0xa, 800, 600);
|
|
}
|
|
|
|
|
|
// Initialize relevant Mac globals
|
|
// (Do this before copying COFF segments, in case they overwrite these globals)
|
|
init_macintosh_globals();
|
|
|
|
/* Copy COFF segments into memory */
|
|
for (i = 0; i < coff->num_sections; i++) {
|
|
coff_section *s = &coff->sections[i];
|
|
|
|
// Don't load a "copy" segment
|
|
if (s->flags & coff_copy)
|
|
continue;
|
|
|
|
if ((s->flags & coff_text) || (s->flags & coff_data)) {
|
|
/* copy text or data section */
|
|
|
|
for (j = 0; j < s->sz; j++)
|
|
pset(s->p_addr+j, 1, s->data[j]);
|
|
|
|
if (strcmp(s->name, "pstart") == 0)
|
|
pc = s->p_addr;
|
|
}
|
|
else if (s->flags & coff_bss) {
|
|
/* Create an empty .bss segment */
|
|
|
|
for (j = 0; j < s->sz; j++)
|
|
pset(s->p_addr+j, 1, 0);
|
|
}
|
|
}
|
|
|
|
if (pc == 0xffffffff) {
|
|
printf("init_state: this unix doesn't contain a pstart segment\n");
|
|
return 0;
|
|
}
|
|
|
|
set_sr(0x2000);
|
|
shoe.pc = pc;
|
|
|
|
// Start the VIA clocks
|
|
gettimeofday(&shoe.start_time, NULL);
|
|
shoe.total_ticks = 0;
|
|
|
|
// Put the adb chip in state 3 (idle)
|
|
shoe.adb.state = 3;
|
|
pthread_mutex_init(&shoe.adb.lock, NULL);
|
|
|
|
for (i=0; i<8; i++) {
|
|
shoe.scsi_devices[i].scsi_id = i;
|
|
shoe.scsi_devices[i].block_size = 0;
|
|
shoe.scsi_devices[i].num_blocks = 0;
|
|
shoe.scsi_devices[i].image_path = "dummy";
|
|
shoe.scsi_devices[i].f = NULL;
|
|
}
|
|
|
|
// Hacky load scsi disk at id 0
|
|
|
|
/*shoe.scsi_devices[0].scsi_id = 0;
|
|
shoe.scsi_devices[0].block_size = 512;
|
|
shoe.scsi_devices[0].num_blocks = 656544;
|
|
shoe.scsi_devices[0].image_path = "priv/Apple_UNIX_3.iso";
|
|
shoe.scsi_devices[0].f = fopen(shoe.scsi_devices[0].image_path, "r+");
|
|
assert(shoe.scsi_devices[0].f);*/
|
|
|
|
shoe.scsi_devices[0].scsi_id = 0;
|
|
shoe.scsi_devices[0].block_size = 512;
|
|
shoe.scsi_devices[0].num_blocks = 195912; //205561;
|
|
shoe.scsi_devices[0].image_path = "priv/aux2.img";
|
|
shoe.scsi_devices[0].f = fopen(shoe.scsi_devices[0].image_path, "r+");
|
|
assert(shoe.scsi_devices[0].f);
|
|
|
|
shoe.scsi_devices[1].scsi_id = 1;
|
|
shoe.scsi_devices[1].block_size = 512;
|
|
shoe.scsi_devices[1].num_blocks = 1048576;
|
|
shoe.scsi_devices[1].image_path = "priv/blank.img";
|
|
shoe.scsi_devices[1].f = fopen(shoe.scsi_devices[1].image_path, "r+");
|
|
assert(shoe.scsi_devices[1].f);
|
|
|
|
/*shoe.scsi_devices[6].scsi_id = 6;
|
|
shoe.scsi_devices[6].block_size = 512;
|
|
shoe.scsi_devices[6].num_blocks = 1280032; //205561;
|
|
shoe.scsi_devices[6].image_path = "priv/macii_fs.img";
|
|
shoe.scsi_devices[6].f = fopen(shoe.scsi_devices[6].image_path, "r+");
|
|
assert(shoe.scsi_devices[6].f);*/
|
|
|
|
/* In the future, we'll need to setup that booter struct for A/UX. */
|
|
|
|
// HACK HACK HACK
|
|
// Not sure why, but it seems that on non-RBV systems, A/UX version >= 1.1.1 does memcpy(0x0, 0x50000, 0x4000), blowing away all the lomem stuff
|
|
// So copy lomem stuff to 0x50000
|
|
|
|
for (i=0; i<0x4000; i++) {
|
|
uint8_t c = pget(i, 1);
|
|
pset(0x50000 + i, 1, c);
|
|
}
|
|
|
|
shoe.a[0] += 0x50000; // A/UX thinks kernel_info should live at 0x50000 + (normal kernel_info addr).
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
#define KEY_SHIFT 1
|
|
|
|
const struct {
|
|
uint8_t code;
|
|
char c;
|
|
uint8_t modifiers;
|
|
} key_codes[] = {
|
|
{0x0, 'A', 0},
|
|
{0x1, 'S', 0},
|
|
{2, 'D', 0},
|
|
{3, 'F', 0},
|
|
{4, 'H', 0},
|
|
{5, 'G', 0},
|
|
{6, 'Z', 0},
|
|
{7, 'X', 0},
|
|
{8, 'C', 0},
|
|
{9, 'V', 0},
|
|
// {0xa ??
|
|
{0xb, 'B', 0},
|
|
{0xc, 'Q', 0},
|
|
{0xd, 'W', 0},
|
|
{0xe, 'E', 0},
|
|
{0xf, 'R', 0},
|
|
{0x10, 'Y', 0},
|
|
{0x11, 'T', 0},
|
|
|
|
{0x12, '1', 0},
|
|
{0x12, '!', KEY_SHIFT},
|
|
|
|
|
|
{0x13, '2', 0},
|
|
{0x13, '@', KEY_SHIFT},
|
|
|
|
{0x14, '3', 0},
|
|
{0x14, '#', KEY_SHIFT},
|
|
|
|
{0x15, '4', 0},
|
|
{0x15, '$', KEY_SHIFT},
|
|
|
|
{0x16, '6', 0},
|
|
{0x16, '^', KEY_SHIFT},
|
|
|
|
{0x17, '5', 0},
|
|
{0x17, '%', KEY_SHIFT},
|
|
|
|
{0x18, '=', 0},
|
|
{0x18, '+', KEY_SHIFT},
|
|
|
|
{0x19, '9', 0},
|
|
{0x19, '(', KEY_SHIFT},
|
|
|
|
{0x1a, '7', 0},
|
|
{0x1a, '&', KEY_SHIFT},
|
|
|
|
{0x1b, '-', 0},
|
|
{0x1b, '_', KEY_SHIFT},
|
|
|
|
{0x1c, '8', 0},
|
|
{0x1c, '*', KEY_SHIFT},
|
|
|
|
{0x1d, '0', 0},
|
|
{0x1d, ')', KEY_SHIFT},
|
|
|
|
{0x1e, ']', 0},
|
|
{0x1e, '}', KEY_SHIFT},
|
|
|
|
{0x1f, 'O', 0},
|
|
{0x20, 'U', 0},
|
|
|
|
{0x21, '[', 0},
|
|
{0x21, '{', KEY_SHIFT},
|
|
|
|
{0x22, 'I', 0},
|
|
{0x23, 'P', 0},
|
|
|
|
{0x24, '\n', 0},
|
|
{0x24, '\r', 0},
|
|
|
|
{0x25, 'L', 0},
|
|
{0x26, 'J', 0},
|
|
|
|
{0x27, '"', KEY_SHIFT},
|
|
{0x27, '\'', 0},
|
|
|
|
{0x28, 'K', 0},
|
|
|
|
{0x29, ';', 0},
|
|
{0x29, ':', KEY_SHIFT},
|
|
|
|
{0x2a, '\\', 0},
|
|
{0x2a, '|', KEY_SHIFT},
|
|
|
|
{0x2b, ',', 0},
|
|
{0x2b, '<', KEY_SHIFT},
|
|
|
|
{0x2c, '/', 0},
|
|
{0x2c, '?', 0},
|
|
|
|
{0x2d, 'N', 0},
|
|
{0x2e, 'M', 0},
|
|
|
|
{0x2f, '.', 0},
|
|
{0x2f, '>', KEY_SHIFT},
|
|
|
|
{0x30, '\t', 0},
|
|
{0x31, ' ', 0},
|
|
|
|
{0x32, '`', 0},
|
|
{0x32, '~', KEY_SHIFT},
|
|
|
|
{0x33, '\b', 0},
|
|
{0x33, 0x7f, 0},
|
|
// {0x34, ??
|
|
// {0x35 // escape char
|
|
// 0x36 // ctrl
|
|
// 0x37 // command
|
|
// 0x38 // shift
|
|
// 0x39 // caps lock
|
|
// 0x3a // option
|
|
// 0x3b // left arrow
|
|
// 0x3c // right arrow
|
|
// 0x3d // down arrow
|
|
// 0x3e // up arrow
|
|
|
|
{0, 0, 0},
|
|
};
|
|
|
|
static uint8_t lookup_key(char c)
|
|
{
|
|
uint32_t i;
|
|
uint8_t upper=toupper(c);
|
|
|
|
for (i=0; key_codes[i].c; i++) {
|
|
if (key_codes[i].c == upper)
|
|
return key_codes[i].code;
|
|
|
|
}
|
|
|
|
return 0xff;
|
|
}
|
|
|
|
static uint8_t lookup_special(int special)
|
|
{
|
|
switch (special) {
|
|
case GLUT_KEY_UP: return 0x3e;
|
|
case GLUT_KEY_DOWN: return 0x3d;
|
|
case GLUT_KEY_LEFT: return 0x3b;
|
|
case GLUT_KEY_RIGHT: return 0x3c;
|
|
default: return 0xff;
|
|
}
|
|
}
|
|
|
|
static void keyboard_add_entry(uint8_t code, uint8_t up)
|
|
{
|
|
uint8_t up_mask = up ? 0x80 : 0;
|
|
uint32_t i;
|
|
int modifiers = glutGetModifiers();
|
|
|
|
assert(pthread_mutex_lock(&shoe.adb.lock) == 0);
|
|
|
|
if ((shoe.key.key_i+1) < KEYBOARD_STATE_MAX_KEYS) {
|
|
if (modifiers & GLUT_ACTIVE_SHIFT) {
|
|
shoe.key.keys[shoe.key.key_i].code_a = 0x38;
|
|
shoe.key.keys[shoe.key.key_i].code_b = 0xff;
|
|
shoe.key.key_i++;
|
|
}
|
|
else if (shoe.key.down_modifiers & GLUT_ACTIVE_SHIFT) {
|
|
shoe.key.keys[shoe.key.key_i].code_a = 0x80 | 0x38;
|
|
shoe.key.keys[shoe.key.key_i].code_b = 0xff;
|
|
shoe.key.key_i++;
|
|
}
|
|
shoe.key.keys[shoe.key.key_i].code_a = code | up_mask;
|
|
shoe.key.keys[shoe.key.key_i].code_b = 0xff;
|
|
shoe.key.key_i++;
|
|
}
|
|
|
|
shoe.key.down_modifiers = modifiers;
|
|
|
|
adb_request_service_request(2);
|
|
|
|
pthread_mutex_unlock(&shoe.adb.lock);
|
|
}
|
|
|
|
void global_mouse_func (int button, int state, int x, int y)
|
|
{
|
|
//if (button != GLUT_LEFT_BUTTON)
|
|
// return ;
|
|
|
|
assert(pthread_mutex_lock(&shoe.adb.lock) == 0);
|
|
|
|
shoe.mouse.button_down = (state == GLUT_DOWN);
|
|
shoe.mouse.changed = 1;
|
|
|
|
adb_request_service_request(3);
|
|
|
|
pthread_mutex_unlock(&shoe.adb.lock);
|
|
|
|
// printf("mouse_func: setting service request\n");
|
|
}
|
|
|
|
static void move_mouse (int x, int y, uint8_t button_down)
|
|
{
|
|
printf("%s: lock\n", __func__); fflush(stdout);
|
|
assert(pthread_mutex_lock(&shoe.adb.lock) == 0);
|
|
|
|
int32_t delta_x = x - shoe.mouse.old_x;
|
|
int32_t delta_y = y - shoe.mouse.old_y;
|
|
|
|
shoe.mouse.old_x = x;
|
|
shoe.mouse.old_y = y;
|
|
|
|
shoe.mouse.delta_x += delta_x;
|
|
shoe.mouse.delta_y += delta_y;
|
|
shoe.mouse.button_down = button_down;
|
|
shoe.mouse.changed = 1;
|
|
|
|
adb_request_service_request(3);
|
|
printf("%s: unlock\n", __func__); fflush(stdout);
|
|
pthread_mutex_unlock(&shoe.adb.lock);
|
|
|
|
// printf("move_mouse: setting service request\n");
|
|
}
|
|
|
|
void global_motion_func (int x, int y)
|
|
{
|
|
move_mouse(x, y, 1);
|
|
}
|
|
|
|
void global_passive_motion_func (int x, int y)
|
|
{
|
|
move_mouse(x, y, 0);
|
|
}
|
|
|
|
void global_keyboard_up_func (unsigned char c, int x, int y)
|
|
{
|
|
uint8_t code = lookup_key(c);
|
|
if (code != 0xff)
|
|
keyboard_add_entry(code, 1);
|
|
}
|
|
|
|
void global_keyboard_down_func (unsigned char c, int x, int y)
|
|
{
|
|
uint8_t code = lookup_key(c);
|
|
if (code != 0xff)
|
|
keyboard_add_entry(code, 0);
|
|
}
|
|
|
|
void global_special_up_func (int special, int x, int y)
|
|
{
|
|
const uint8_t code = lookup_special(special);
|
|
if (code != 0xff)
|
|
keyboard_add_entry(code, 1);
|
|
}
|
|
|
|
void global_special_down_func (int special, int x, int y)
|
|
{
|
|
const uint8_t code = lookup_special(special);
|
|
if (code != 0xff)
|
|
keyboard_add_entry(code, 0);
|
|
}
|
|
|
|
void timer_func (int arg)
|
|
{
|
|
glutTimerFunc(15, timer_func, 0); // 66.67hz is the usual refresh interval (right?)
|
|
|
|
uint32_t slotnum;
|
|
for (slotnum = 0; slotnum < 16; slotnum++) {
|
|
if (shoe.slots[slotnum].glut_window_id == -1)
|
|
continue;
|
|
|
|
glutSetWindow(shoe.slots[slotnum].glut_window_id);
|
|
glutPostRedisplay();
|
|
}
|
|
}
|
|
|
|
//void _macii_load_video_rom(const char *path);
|
|
int main (int argc, char **argv)
|
|
{
|
|
coff_file *coff;
|
|
uint32_t romsize;
|
|
uint8_t *rom;
|
|
pthread_t pid;
|
|
|
|
//_macii_load_video_rom("tfb_rom");
|
|
|
|
rom = loadrom("priv/macii.rom", &romsize);
|
|
if (rom == NULL)
|
|
return 0;
|
|
//printf(")
|
|
|
|
/*if (romsize != (256 * 1024)) {
|
|
printf("Bogus rom size (%u bytes)\n", romsize);
|
|
return 0;
|
|
}*/
|
|
|
|
coff = coff_parser("priv/unix");
|
|
if (coff == NULL) {
|
|
printf("main: coff_parser() failed to load \"unix\"\n");
|
|
return 0;
|
|
}
|
|
|
|
int dummyargc = 1;
|
|
glutInit(&dummyargc, argv);
|
|
|
|
if (!init_state(coff, rom, romsize))
|
|
return 0;
|
|
free(rom);
|
|
|
|
pthread_create(&pid, NULL, ui_thread, NULL);
|
|
|
|
glutTimerFunc(15, timer_func, 0);
|
|
glutMainLoop();
|
|
while (1) sleep(1);
|
|
return 0;
|
|
}
|
|
|