mirror of
https://github.com/fernandotcl/TinyEMU.git
synced 2025-10-14 01:58:37 +08:00
350 lines
9.5 KiB
C
350 lines
9.5 KiB
C
/*
|
|
* JS emulator main
|
|
*
|
|
* Copyright (c) 2016-2017 Fabrice Bellard
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*/
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <inttypes.h>
|
|
#include <assert.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
#include <time.h>
|
|
#include <emscripten.h>
|
|
|
|
#include "cutils.h"
|
|
#include "iomem.h"
|
|
#include "virtio.h"
|
|
#include "machine.h"
|
|
#include "list.h"
|
|
#include "fbuf.h"
|
|
|
|
void virt_machine_run(void *opaque);
|
|
|
|
/* provided in lib.js */
|
|
extern void console_write(void *opaque, const uint8_t *buf, int len);
|
|
extern void console_get_size(int *pw, int *ph);
|
|
extern void fb_refresh(void *opaque, void *data,
|
|
int x, int y, int w, int h, int stride);
|
|
extern void net_recv_packet(EthernetDevice *bs,
|
|
const uint8_t *buf, int len);
|
|
|
|
static uint8_t console_fifo[1024];
|
|
static int console_fifo_windex;
|
|
static int console_fifo_rindex;
|
|
static int console_fifo_count;
|
|
static BOOL console_resize_pending;
|
|
|
|
static int global_width;
|
|
static int global_height;
|
|
static VirtMachine *global_vm;
|
|
static BOOL global_carrier_state;
|
|
|
|
static int console_read(void *opaque, uint8_t *buf, int len)
|
|
{
|
|
int out_len, l;
|
|
len = min_int(len, console_fifo_count);
|
|
console_fifo_count -= len;
|
|
out_len = 0;
|
|
while (len != 0) {
|
|
l = min_int(len, sizeof(console_fifo) - console_fifo_rindex);
|
|
memcpy(buf + out_len, console_fifo + console_fifo_rindex, l);
|
|
len -= l;
|
|
out_len += l;
|
|
console_fifo_rindex += l;
|
|
if (console_fifo_rindex == sizeof(console_fifo))
|
|
console_fifo_rindex = 0;
|
|
}
|
|
return out_len;
|
|
}
|
|
|
|
/* called from JS */
|
|
void console_queue_char(int c)
|
|
{
|
|
if (console_fifo_count < sizeof(console_fifo)) {
|
|
console_fifo[console_fifo_windex] = c;
|
|
if (++console_fifo_windex == sizeof(console_fifo))
|
|
console_fifo_windex = 0;
|
|
console_fifo_count++;
|
|
}
|
|
}
|
|
|
|
/* called from JS */
|
|
void display_key_event(int is_down, int key_code)
|
|
{
|
|
if (global_vm) {
|
|
vm_send_key_event(global_vm, is_down, key_code);
|
|
}
|
|
}
|
|
|
|
/* called from JS */
|
|
static int mouse_last_x, mouse_last_y, mouse_last_buttons;
|
|
|
|
void display_mouse_event(int dx, int dy, int buttons)
|
|
{
|
|
if (global_vm) {
|
|
if (vm_mouse_is_absolute(global_vm) || 1) {
|
|
dx = min_int(dx, global_width - 1);
|
|
dy = min_int(dy, global_height - 1);
|
|
dx = (dx * VIRTIO_INPUT_ABS_SCALE) / global_width;
|
|
dy = (dy * VIRTIO_INPUT_ABS_SCALE) / global_height;
|
|
} else {
|
|
/* relative mouse is not supported */
|
|
dx = 0;
|
|
dy = 0;
|
|
}
|
|
mouse_last_x = dx;
|
|
mouse_last_y = dy;
|
|
mouse_last_buttons = buttons;
|
|
vm_send_mouse_event(global_vm, dx, dy, 0, buttons);
|
|
}
|
|
}
|
|
|
|
/* called from JS */
|
|
void display_wheel_event(int dz)
|
|
{
|
|
if (global_vm) {
|
|
vm_send_mouse_event(global_vm, mouse_last_x, mouse_last_y, dz,
|
|
mouse_last_buttons);
|
|
}
|
|
}
|
|
|
|
/* called from JS */
|
|
void net_write_packet(const uint8_t *buf, int buf_len)
|
|
{
|
|
EthernetDevice *net = global_vm->net;
|
|
if (net) {
|
|
net->device_write_packet(net, buf, buf_len);
|
|
}
|
|
}
|
|
|
|
/* called from JS */
|
|
void net_set_carrier(BOOL carrier_state)
|
|
{
|
|
EthernetDevice *net;
|
|
global_carrier_state = carrier_state;
|
|
if (global_vm && global_vm->net) {
|
|
net = global_vm->net;
|
|
net->device_set_carrier(net, carrier_state);
|
|
}
|
|
}
|
|
|
|
static void fb_refresh1(FBDevice *fb_dev, void *opaque,
|
|
int x, int y, int w, int h)
|
|
{
|
|
int stride = fb_dev->stride;
|
|
fb_refresh(opaque, fb_dev->fb_data + y * stride + x * 4, x, y, w, h,
|
|
stride);
|
|
}
|
|
|
|
static CharacterDevice *console_init(void)
|
|
{
|
|
CharacterDevice *dev;
|
|
console_resize_pending = TRUE;
|
|
dev = mallocz(sizeof(*dev));
|
|
dev->write_data = console_write;
|
|
dev->read_data = console_read;
|
|
return dev;
|
|
}
|
|
|
|
typedef struct {
|
|
VirtMachineParams *p;
|
|
int ram_size;
|
|
char *cmdline;
|
|
BOOL has_network;
|
|
char *pwd;
|
|
} VMStartState;
|
|
|
|
static void init_vm(void *arg);
|
|
static void init_vm_fs(void *arg);
|
|
static void init_vm_drive(void *arg);
|
|
|
|
void vm_start(const char *url, int ram_size, const char *cmdline,
|
|
const char *pwd, int width, int height, BOOL has_network)
|
|
{
|
|
VMStartState *s;
|
|
|
|
s = mallocz(sizeof(*s));
|
|
s->ram_size = ram_size;
|
|
s->cmdline = strdup(cmdline);
|
|
if (pwd)
|
|
s->pwd = strdup(pwd);
|
|
global_width = width;
|
|
global_height = height;
|
|
s->has_network = has_network;
|
|
s->p = mallocz(sizeof(VirtMachineParams));
|
|
virt_machine_set_defaults(s->p);
|
|
virt_machine_load_config_file(s->p, url, init_vm_fs, s);
|
|
}
|
|
|
|
static void init_vm_fs(void *arg)
|
|
{
|
|
VMStartState *s = arg;
|
|
VirtMachineParams *p = s->p;
|
|
|
|
if (p->fs_count > 0) {
|
|
assert(p->fs_count == 1);
|
|
p->tab_fs[0].fs_dev = fs_net_init(p->tab_fs[0].filename,
|
|
init_vm_drive, s);
|
|
if (s->pwd) {
|
|
fs_net_set_pwd(p->tab_fs[0].fs_dev, s->pwd);
|
|
}
|
|
} else {
|
|
init_vm_drive(s);
|
|
}
|
|
}
|
|
|
|
static void init_vm_drive(void *arg)
|
|
{
|
|
VMStartState *s = arg;
|
|
VirtMachineParams *p = s->p;
|
|
|
|
if (p->drive_count > 0) {
|
|
assert(p->drive_count == 1);
|
|
p->tab_drive[0].block_dev =
|
|
block_device_init_http(p->tab_drive[0].filename,
|
|
131072,
|
|
init_vm, s);
|
|
} else {
|
|
init_vm(s);
|
|
}
|
|
}
|
|
|
|
static void init_vm(void *arg)
|
|
{
|
|
VMStartState *s = arg;
|
|
VirtMachine *m;
|
|
VirtMachineParams *p = s->p;
|
|
int i;
|
|
|
|
p->rtc_real_time = TRUE;
|
|
p->ram_size = s->ram_size << 20;
|
|
if (s->cmdline && s->cmdline[0] != '\0') {
|
|
vm_add_cmdline(s->p, s->cmdline);
|
|
}
|
|
|
|
if (global_width > 0 && global_height > 0) {
|
|
/* enable graphic output if needed */
|
|
if (!p->display_device)
|
|
p->display_device = strdup("simplefb");
|
|
p->width = global_width;
|
|
p->height = global_height;
|
|
} else {
|
|
p->console = console_init();
|
|
}
|
|
|
|
if (p->eth_count > 0 && !s->has_network) {
|
|
/* remove the interfaces */
|
|
for(i = 0; i < p->eth_count; i++) {
|
|
free(p->tab_eth[i].ifname);
|
|
free(p->tab_eth[i].driver);
|
|
}
|
|
p->eth_count = 0;
|
|
}
|
|
|
|
if (p->eth_count > 0) {
|
|
EthernetDevice *net;
|
|
int i;
|
|
assert(p->eth_count == 1);
|
|
net = mallocz(sizeof(EthernetDevice));
|
|
net->mac_addr[0] = 0x02;
|
|
for(i = 1; i < 6; i++)
|
|
net->mac_addr[i] = (int)(emscripten_random() * 256);
|
|
net->write_packet = net_recv_packet;
|
|
net->opaque = NULL;
|
|
p->tab_eth[0].net = net;
|
|
}
|
|
|
|
m = virt_machine_init(p);
|
|
global_vm = m;
|
|
|
|
virt_machine_free_config(s->p);
|
|
|
|
if (m->net) {
|
|
m->net->device_set_carrier(m->net, global_carrier_state);
|
|
}
|
|
|
|
free(s->p);
|
|
free(s->cmdline);
|
|
if (s->pwd) {
|
|
memset(s->pwd, 0, strlen(s->pwd));
|
|
free(s->pwd);
|
|
}
|
|
free(s);
|
|
|
|
emscripten_async_call(virt_machine_run, m, 0);
|
|
}
|
|
|
|
/* need to be long enough to hide the non zero delay of setTimeout(_, 0) */
|
|
#define MAX_EXEC_TOTAL_CYCLE 3000000
|
|
#define MAX_EXEC_CYCLE 200000
|
|
|
|
#define MAX_SLEEP_TIME 10 /* in ms */
|
|
|
|
void virt_machine_run(void *opaque)
|
|
{
|
|
VirtMachine *m = opaque;
|
|
int delay, i;
|
|
FBDevice *fb_dev;
|
|
|
|
if (m->console_dev && virtio_console_can_write_data(m->console_dev)) {
|
|
uint8_t buf[128];
|
|
int ret, len;
|
|
len = virtio_console_get_write_len(m->console_dev);
|
|
len = min_int(len, sizeof(buf));
|
|
ret = m->console->read_data(m->console->opaque, buf, len);
|
|
if (ret > 0)
|
|
virtio_console_write_data(m->console_dev, buf, ret);
|
|
if (console_resize_pending) {
|
|
int w, h;
|
|
console_get_size(&w, &h);
|
|
virtio_console_resize_event(m->console_dev, w, h);
|
|
console_resize_pending = FALSE;
|
|
}
|
|
}
|
|
|
|
fb_dev = m->fb_dev;
|
|
if (fb_dev) {
|
|
/* refresh the display */
|
|
fb_dev->refresh(fb_dev, fb_refresh1, NULL);
|
|
}
|
|
|
|
i = 0;
|
|
for(;;) {
|
|
/* wait for an event: the only asynchronous event is the RTC timer */
|
|
delay = virt_machine_get_sleep_duration(m, MAX_SLEEP_TIME);
|
|
if (delay != 0 || i >= MAX_EXEC_TOTAL_CYCLE / MAX_EXEC_CYCLE)
|
|
break;
|
|
virt_machine_interp(m, MAX_EXEC_CYCLE);
|
|
i++;
|
|
}
|
|
|
|
if (delay == 0) {
|
|
emscripten_async_call(virt_machine_run, m, 0);
|
|
} else {
|
|
emscripten_async_call(virt_machine_run, m, MAX_SLEEP_TIME);
|
|
}
|
|
}
|
|
|