genode/repos/ports/src/app/seoul/console.cc

382 lines
10 KiB
C++

/*
* \brief Manager of all VM requested console functionality
* \author Markus Partheymueller
* \author Norman Feske
* \author Alexander Boettcher
* \date 2012-07-31
*/
/*
* Copyright (C) 2011-2017 Genode Labs GmbH
* Copyright (C) 2012 Intel Corporation
*
* This file is distributed under the terms of the GNU General Public License
* version 2.
*
* The code is partially based on the Vancouver VMM, which is distributed
* under the terms of the GNU General Public License version 2.
*
* Modifications by Intel Corporation are contributed under the terms and
* conditions of the GNU General Public License version 2.
*/
/* base includes */
#include <util/register.h>
/* nitpicker graphics backend */
#include <nitpicker_gfx/tff_font.h>
#include <nul/motherboard.h>
#include <host/screen.h>
/* local includes */
#include "console.h"
extern char _binary_mono_tff_start[];
static Tff_font::Static_glyph_buffer<4096> glyph_buffer { };
static Tff_font default_font(_binary_mono_tff_start, glyph_buffer);
static struct {
Genode::uint64_t checksum1 = 0;
Genode::uint64_t checksum2 = 0;
unsigned unchanged = 0;
bool cmp_even = 1;
bool active = false;
bool revoked = false;
bool vga_update= false; /* update indirectly by vbios */
} fb_state;
/**
* Layout of PS/2 mouse packet
*/
struct Ps2_mouse_packet : Genode::Register<32>
{
struct Packet_size : Bitfield<0, 3> { };
struct Left_button : Bitfield<8, 1> { };
struct Middle_button : Bitfield<9, 1> { };
struct Right_button : Bitfield<10, 1> { };
struct Rx_high : Bitfield<12, 1> { };
struct Ry_high : Bitfield<13, 1> { };
struct Rx_low : Bitfield<16, 8> { };
struct Ry_low : Bitfield<24, 8> { };
};
static bool mouse_event(Input::Event const &ev)
{
using namespace Input;
bool result = false;
auto mouse_button = [] (Keycode key) {
return key == BTN_LEFT || key == BTN_MIDDLE || key == BTN_RIGHT; };
ev.handle_press([&] (Keycode key, Genode::Codepoint) {
result |= mouse_button(key); });
ev.handle_release([&] (Keycode key) {
result |= mouse_button(key); });
result |= ev.absolute_motion() || ev.relative_motion();
return result;
}
/**
* Convert Genode::Input event to PS/2 packet
*
* This function updates _left, _middle, and _right as a side effect.
*/
unsigned Seoul::Console::_input_to_ps2mouse(Input::Event const &ev)
{
/* track state of mouse buttons */
auto apply_button = [] (Input::Event const &ev, Input::Keycode key, bool &state) {
if (ev.key_press (key)) state = true;
if (ev.key_release(key)) state = false;
};
apply_button(ev, Input::BTN_LEFT, _left);
apply_button(ev, Input::BTN_MIDDLE, _middle);
apply_button(ev, Input::BTN_RIGHT, _right);
int rx = 0;
int ry = 0;
ev.handle_absolute_motion([&] (int x, int y) {
static int ox = 0, oy = 0;
rx = x - ox; ry = y - oy;
ox = x; oy = y;
});
ev.handle_relative_motion([&] (int x, int y) { rx = x; ry = y; });
/* clamp relative motion vector to bounds */
int const boundary = 200;
rx = Genode::min(boundary, Genode::max(-boundary, rx));
ry = -Genode::min(boundary, Genode::max(-boundary, ry));
/* assemble PS/2 packet */
Ps2_mouse_packet::access_t packet = 0;
Ps2_mouse_packet::Packet_size::set (packet, 3);
Ps2_mouse_packet::Left_button::set (packet, _left);
Ps2_mouse_packet::Middle_button::set(packet, _middle);
Ps2_mouse_packet::Right_button::set (packet, _right);
Ps2_mouse_packet::Rx_high::set (packet, (rx >> 8) & 1);
Ps2_mouse_packet::Ry_high::set (packet, (ry >> 8) & 1);
Ps2_mouse_packet::Rx_low::set (packet, rx & 0xff);
Ps2_mouse_packet::Ry_low::set (packet, ry & 0xff);
return packet;
}
/* bus callbacks */
bool Seoul::Console::receive(MessageConsole &msg)
{
if (msg.type == MessageConsole::TYPE_ALLOC_VIEW) {
_guest_fb = msg.ptr;
_regs = msg.regs;
msg.view = 0;
} else if (msg.type == MessageConsole::TYPE_SWITCH_VIEW) {
/* XXX: For now, we only have one view. */
} else if (msg.type == MessageConsole::TYPE_GET_MODEINFO) {
enum {
MEMORY_MODEL_TEXT = 0,
MEMORY_MODEL_DIRECT_COLOR = 6,
};
/*
* We supply two modes to the guest, text mode and one
* configured graphics mode 16-bit.
*/
if (msg.index == 0) {
msg.info->_vesa_mode = 3;
msg.info->attr = 0x1;
msg.info->resolution[0] = 80;
msg.info->resolution[1] = 25;
msg.info->bytes_per_scanline = 80*2;
msg.info->bytes_scanline = 80*2;
msg.info->bpp = 4;
msg.info->memory_model = MEMORY_MODEL_TEXT;
msg.info->phys_base = 0xb8000;
msg.info->_phys_size = 0x8000;
return true;
} else if (msg.index == 1) {
/*
* It's important to set the _vesa_mode field, otherwise the
* device model is going to ignore this mode.
*/
msg.info->_vesa_mode = 0x114;
msg.info->attr = 0x39f;
msg.info->resolution[0] = _fb_mode.width();
msg.info->resolution[1] = _fb_mode.height();
msg.info->bytes_per_scanline = _fb_mode.width()*2;
msg.info->bytes_scanline = _fb_mode.width()*2;
msg.info->bpp = 16;
msg.info->memory_model = MEMORY_MODEL_DIRECT_COLOR;
msg.info->vbe1[0] = 0x5; /* red mask size */
msg.info->vbe1[1] = 0xb; /* red field position */
msg.info->vbe1[2] = 0x6; /* green mask size */
msg.info->vbe1[3] = 0x5; /* green field position */
msg.info->vbe1[4] = 0x5; /* blue mask size */
msg.info->vbe1[5] = 0x0; /* blue field position */
msg.info->vbe1[6] = 0x0; /* reserved mask size */
msg.info->vbe1[7] = 0x0; /* reserved field position */
msg.info->colormode = 0x0; /* direct color mode info */
msg.info->phys_base = 0xe0000000;
msg.info->_phys_size = _fb_mode.width()*_fb_mode.height()*2;
return true;
} else return false;
}
return true;
}
void Screen::vga_updated()
{
fb_state.vga_update = true;
}
bool Seoul::Console::receive(MessageMemRegion &msg)
{
/* we had a fault in the text framebuffer */
bool reactivate = (msg.page >= 0xb8 && msg.page <= 0xbf);
/* vga memory got changed indirectly by vbios */
if (fb_state.vga_update) {
fb_state.vga_update = false;
if (!fb_state.active)
reactivate = true;
}
if (reactivate) {
if (!fb_state.active) fb_state.active = true;
Logging::printf("Reactivating text buffer loop.\n");
MessageTimer msg(_timer, _unsynchronized_motherboard.clock()->abstime(1, 1000));
_unsynchronized_motherboard.bus_timer.send(msg);
}
return false;
}
unsigned Seoul::Console::_handle_fb()
{
if (!_guest_fb || !_regs)
return 0;
enum { TEXT_MODE = 0 };
/* transfer text buffer content into chunky canvas */
if (_regs->mode == TEXT_MODE) {
if (fb_state.revoked || !fb_state.active)
return 0;
memset(_pixels, 0, _fb_size);
if (fb_state.cmp_even) fb_state.checksum1 = 0;
else fb_state.checksum2 = 0;
for (int j=0; j<25; j++) {
for (int i=0; i<80; i++) {
Text_painter::Position const where(i*8, j*15);
char character = *((char *) (_guest_fb +(_regs->offset << 1) +j*80*2+i*2));
char colorvalue = *((char *) (_guest_fb+(_regs->offset << 1)+j*80*2+i*2+1));
char buffer[2]; buffer[0] = character; buffer[1] = 0;
char fg = colorvalue & 0xf;
if (fg == 0x8) fg = 0x7;
unsigned lum = ((fg & 0x8) >> 3)*127;
Genode::Color color(((fg & 0x4) >> 2)*127+lum, /* R+luminosity */
((fg & 0x2) >> 1)*127+lum, /* G+luminosity */
(fg & 0x1)*127+lum /* B+luminosity */);
Text_painter::paint(_surface, where, default_font, color, buffer);
/* Checksum for comparing */
if (fb_state.cmp_even) fb_state.checksum1 += character;
else fb_state.checksum2 += character;
}
}
fb_state.cmp_even = !fb_state.cmp_even;
/* compare checksums to detect changed buffer */
if (fb_state.checksum1 != fb_state.checksum2) {
fb_state.unchanged = 0;
_framebuffer.refresh(0, 0, _fb_mode.width(), _fb_mode.height());
return 100;
}
if (++fb_state.unchanged < 10)
return fb_state.unchanged * 30;
/* if we copy the same data 10 times, unmap the text buffer from guest */
_env.rm().detach((void *)_guest_fb);
_env.rm().attach_at(_guest_fb_ds, (Genode::addr_t)_guest_fb);
fb_state.unchanged = 0;
fb_state.active = false;
Logging::printf("Deactivated text buffer loop.\n");
return 0;
}
if (!fb_state.revoked) {
_env.rm().detach((void *)_guest_fb);
_env.rm().attach_at(_framebuffer.dataspace(),
(Genode::addr_t)_guest_fb);
fb_state.revoked = true;
}
_framebuffer.refresh(0, 0, _fb_mode.width(), _fb_mode.height());
return 10;
}
void Seoul::Console::_handle_input()
{
_input.for_each_event([&] (Input::Event const &ev) {
if (!fb_state.active) {
fb_state.active = true;
MessageTimer msg(_timer, _motherboard()->clock()->abstime(1, 1000));
_motherboard()->bus_timer.send(msg);
}
/* update mouse model (PS2) */
if (mouse_event(ev)) {
MessageInput msg(0x10001, _input_to_ps2mouse(ev));
_motherboard()->bus_input.send(msg);
}
ev.handle_press([&] (Input::Keycode key, Genode::Codepoint) {
if (key <= 0xee)
_vkeyb.handle_keycode_press(key); });
ev.handle_release([&] (Input::Keycode key) {
if (key <= 0xee)
_vkeyb.handle_keycode_release(key); });
});
}
void Seoul::Console::register_host_operations(Motherboard &motherboard)
{
motherboard.bus_console .add(this, receive_static<MessageConsole>);
motherboard.bus_memregion.add(this, receive_static<MessageMemRegion>);
motherboard.bus_timeout .add(this, receive_static<MessageTimeout>);
MessageTimer msg;
if (!motherboard.bus_timer.send(msg))
Logging::panic("%s can't get a timer", __PRETTY_FUNCTION__);
_timer = msg.nr;
}
bool Seoul::Console::receive(MessageTimeout &msg) {
if (msg.nr != _timer)
return false;
unsigned next_timeout_ms = _handle_fb();
if (next_timeout_ms) {
MessageTimer msg_t(_timer, _unsynchronized_motherboard.clock()->abstime(next_timeout_ms, 1000));
_unsynchronized_motherboard.bus_timer.send(msg_t);
}
return true;
}
Seoul::Console::Console(Genode::Env &env, Synced_motherboard &mb,
Motherboard &unsynchronized_motherboard,
Framebuffer::Connection &framebuffer,
Genode::Dataspace_capability guest_fb_ds)
:
_env(env),
_unsynchronized_motherboard(unsynchronized_motherboard),
_motherboard(mb),
_framebuffer(framebuffer),
_guest_fb_ds(guest_fb_ds),
_fb_mode(_framebuffer.mode()),
_fb_size(Genode::Dataspace_client(_framebuffer.dataspace()).size()),
_pixels(_env.rm().attach(_framebuffer.dataspace())),
_surface(reinterpret_cast<Genode::Pixel_rgb565 *>(_pixels),
Genode::Surface_base::Area(_fb_mode.width(),
_fb_mode.height()))
{
_input.sigh(_signal_input);
}