From d33c1d95929356682fb06083d1da2d66605649f4 Mon Sep 17 00:00:00 2001 From: Ali Saidi Date: Fri, 11 Feb 2011 18:29:35 -0600 Subject: VNC: Add VNC server to M5 --- src/base/vnc/SConscript | 48 ++++ src/base/vnc/VncServer.py | 45 +++ src/base/vnc/convert.cc | 139 +++++++++ src/base/vnc/convert.hh | 141 ++++++++++ src/base/vnc/vncserver.cc | 703 ++++++++++++++++++++++++++++++++++++++++++++++ src/base/vnc/vncserver.hh | 475 +++++++++++++++++++++++++++++++ 6 files changed, 1551 insertions(+) create mode 100644 src/base/vnc/SConscript create mode 100644 src/base/vnc/VncServer.py create mode 100644 src/base/vnc/convert.cc create mode 100644 src/base/vnc/convert.hh create mode 100644 src/base/vnc/vncserver.cc create mode 100644 src/base/vnc/vncserver.hh (limited to 'src/base/vnc') diff --git a/src/base/vnc/SConscript b/src/base/vnc/SConscript new file mode 100644 index 000000000..c92676555 --- /dev/null +++ b/src/base/vnc/SConscript @@ -0,0 +1,48 @@ +# -*- mode:python -*- + +# Copyright (c) 2010 ARM Limited +# All rights reserved. +# +# The license below extends only to copyright in the software and shall +# not be construed as granting a license to any other intellectual +# property including but not limited to intellectual property relating +# to a hardware implementation of the functionality of the software +# licensed hereunder. You may use the software subject to the license +# terms below provided that you ensure that this notice is replicated +# unmodified and in its entirety in all distributions of the software, +# modified or unmodified, in source code or in binary form. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer; +# 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; +# neither the name of the copyright holders nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# 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. +# +# Authors: William Wang + +Import('*') + +if env['FULL_SYSTEM']: + SimObject('VncServer.py') + Source('vncserver.cc') + TraceFlag('VNC') + +Source('convert.cc') + diff --git a/src/base/vnc/VncServer.py b/src/base/vnc/VncServer.py new file mode 100644 index 000000000..21eb3ed28 --- /dev/null +++ b/src/base/vnc/VncServer.py @@ -0,0 +1,45 @@ +# Copyright (c) 2010 ARM Limited +# All rights reserved. +# +# The license below extends only to copyright in the software and shall +# not be construed as granting a license to any other intellectual +# property including but not limited to intellectual property relating +# to a hardware implementation of the functionality of the software +# licensed hereunder. You may use the software subject to the license +# terms below provided that you ensure that this notice is replicated +# unmodified and in its entirety in all distributions of the software, +# modified or unmodified, in source code or in binary form. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer; +# 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; +# neither the name of the copyright holders nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# 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. +# +# Authors: William Wang + +from m5.SimObject import SimObject +from m5.params import * +from m5.proxy import * + +class VncServer(SimObject): + type = 'VncServer' + port = Param.TcpPort(5900, "listen port") + number = Param.Int(0, "vnc client number") diff --git a/src/base/vnc/convert.cc b/src/base/vnc/convert.cc new file mode 100644 index 000000000..ea7a9b1c5 --- /dev/null +++ b/src/base/vnc/convert.cc @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2011 ARM Limited + * All rights reserved + * + * The license below extends only to copyright in the software and shall + * not be construed as granting a license to any other intellectual + * property including but not limited to intellectual property relating + * to a hardware implementation of the functionality of the software + * licensed hereunder. You may use the software subject to the license + * terms below provided that you ensure that this notice is replicated + * unmodified and in its entirety in all distributions of the software, + * modified or unmodified, in source code or in binary form. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer; + * 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; + * neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * 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. + * + * Authors: Ali Saidi + * William Wang + */ + +#include + +#include "base/misc.hh" +#include "base/vnc/convert.hh" + +/** @file + * This file provides conversion functions for a variety of video modes + */ + +VideoConvert::VideoConvert(Mode input_mode, Mode output_mode, int _width, + int _height) + : inputMode(input_mode), outputMode(output_mode), width(_width), + height(_height) +{ + if (inputMode != bgr565 && inputMode != rgb565 && inputMode != bgr8888) + fatal("Only support converting from bgr565, rdb565, and bgr8888\n"); + + if (outputMode != rgb8888) + fatal("Only support converting to rgb8888\n"); + + assert(0 < height && height < 4000); + assert(0 < width && width < 4000); +} + +VideoConvert::~VideoConvert() +{ +} + +uint8_t* +VideoConvert::convert(uint8_t *fb) +{ + switch (inputMode) { + case bgr565: + return m565rgb8888(fb, true); + case rgb565: + return m565rgb8888(fb, false); + case bgr8888: + return bgr8888rgb8888(fb); + default: + panic("Unimplemented Mode\n"); + } +} + +uint8_t* +VideoConvert::m565rgb8888(uint8_t *fb, bool bgr) +{ + uint8_t *out = new uint8_t[area() * sizeof(uint32_t)]; + uint32_t *out32 = (uint32_t*)out; + + uint16_t *in16 = (uint16_t*)fb; + + for (int x = 0; x < area(); x++) { + Bgr565 inpx; + Rgb8888 outpx = 0; + + inpx = in16[x]; + + if (bgr) { + outpx.red = inpx.blue << 3; + outpx.green = inpx.green << 2; + outpx.blue = inpx.red << 3; + } else { + outpx.blue = inpx.blue << 3; + outpx.green = inpx.green << 2; + outpx.red = inpx.red << 3; + } + + out32[x] = outpx; + } + + return out; +} + + +uint8_t* +VideoConvert::bgr8888rgb8888(uint8_t *fb) +{ + uint8_t *out = new uint8_t[area() * sizeof(uint32_t)]; + uint32_t *out32 = (uint32_t*)out; + + uint32_t *in32 = (uint32_t*)fb; + + for (int x = 0; x < area(); x++) { + Rgb8888 outpx = 0; + Bgr8888 inpx; + + + inpx = in32[x]; + + outpx.red = inpx.blue; + outpx.green = inpx.green; + outpx.blue = inpx.red; + + out32[x] = outpx; + } + + return out; +} + diff --git a/src/base/vnc/convert.hh b/src/base/vnc/convert.hh new file mode 100644 index 000000000..68a21d677 --- /dev/null +++ b/src/base/vnc/convert.hh @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2011 ARM Limited + * All rights reserved + * + * The license below extends only to copyright in the software and shall + * not be construed as granting a license to any other intellectual + * property including but not limited to intellectual property relating + * to a hardware implementation of the functionality of the software + * licensed hereunder. You may use the software subject to the license + * terms below provided that you ensure that this notice is replicated + * unmodified and in its entirety in all distributions of the software, + * modified or unmodified, in source code or in binary form. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer; + * 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; + * neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * 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. + * + * Authors: Ali Saidi + */ + +/** @file + * This file provides conversion functions for a variety of video modes + */ + +#ifndef __BASE_VNC_CONVERT_HH__ +#define __BASE_VNC_CONVERT_HH__ + +#include "base/bitunion.hh" + +class VideoConvert +{ + public: + enum Mode { + UnknownMode, + bgr565, + rgb565, + bgr8888, + rgb8888, + rgb888, + bgr888, + bgr444, + bgr4444, + rgb444, + rgb4444, + }; + + // supports bpp32 RGB (bmp) and bpp16 5:6:5 mode BGR (linux) + BitUnion32(Rgb8888) + Bitfield<7,0> blue; + Bitfield<15,8> green; + Bitfield<23,16> red; + Bitfield<31,24> alpha; + EndBitUnion(Rgb8888) + + BitUnion32(Bgr8888) + Bitfield<7,0> red; + Bitfield<15,8> green; + Bitfield<23,16> blue; + Bitfield<31,24> alpha; + EndBitUnion(Bgr8888) + + BitUnion16(Bgr565) + Bitfield<4,0> red; + Bitfield<10,5> green; + Bitfield<15,11> blue; + EndBitUnion(Bgr565) + + BitUnion16(Rgb565) + Bitfield<4,0> red; + Bitfield<10,5> green; + Bitfield<15,11> blue; + EndBitUnion(Rgb565) + + /** Setup the converter with the given parameters + * @param input_mode type of data that will be provided + * @param output_mode type of data that should be output + * @param _width width of the frame buffer + * @param _height height of the frame buffer + */ + VideoConvert(Mode input_mode, Mode output_mode, int _width, int _height); + + /** Destructor + */ + ~VideoConvert(); + + /** Convert the provided frame buffer data into the format specified in the + * constructor. + * @param fb the frame buffer to convert + * @return the converted data (user must free) + */ + uint8_t* convert(uint8_t *fb); + + /** Return the number of pixels that this buffer specifies + * @return number of pixels + */ + int area() { return width * height; } + + private: + + /** + * Convert a bgr8888 input to rgb8888. + * @param fb the data to convert + * @return converted data + */ + uint8_t* bgr8888rgb8888(uint8_t *fb); + + /** + * Convert a bgr565 or rgb565 input to rgb8888. + * @param fb the data to convert + * @param bgr true if the input data is bgr565 + * @return converted data + */ + uint8_t* m565rgb8888(uint8_t *fb, bool bgr); + + Mode inputMode; + Mode outputMode; + int width; + int height; +}; + +#endif // __BASE_VNC_CONVERT_HH__ + diff --git a/src/base/vnc/vncserver.cc b/src/base/vnc/vncserver.cc new file mode 100644 index 000000000..8936fa67b --- /dev/null +++ b/src/base/vnc/vncserver.cc @@ -0,0 +1,703 @@ +/* + * Copyright (c) 2010 ARM Limited + * All rights reserved + * + * The license below extends only to copyright in the software and shall + * not be construed as granting a license to any other intellectual + * property including but not limited to intellectual property relating + * to a hardware implementation of the functionality of the software + * licensed hereunder. You may use the software subject to the license + * terms below provided that you ensure that this notice is replicated + * unmodified and in its entirety in all distributions of the software, + * modified or unmodified, in source code or in binary form. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer; + * 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; + * neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * 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. + * + * Authors: Ali Saidi + * William Wang + */ + +/** @file + * Implementiation of a VNC server + */ + +#include + +#include +#include +#include +#include +#include + +#include "base/atomicio.hh" +#include "base/misc.hh" +#include "base/socket.hh" +#include "base/trace.hh" +#include "base/vnc/vncserver.hh" +#include "sim/byteswap.hh" + +using namespace std; + +/** + * Poll event for the listen socket + */ +VncServer::ListenEvent::ListenEvent(VncServer *vs, int fd, int e) + : PollEvent(fd, e), vncserver(vs) +{ +} + +void +VncServer::ListenEvent::process(int revent) +{ + vncserver->accept(); +} + +/** + * Poll event for the data socket + */ +VncServer::DataEvent::DataEvent(VncServer *vs, int fd, int e) + : PollEvent(fd, e), vncserver(vs) +{ +} + +void +VncServer::DataEvent::process(int revent) +{ + if (revent & POLLIN) + vncserver->data(); + else if (revent & POLLNVAL) + vncserver->detach(); +} + +/** + * VncServer + */ +VncServer::VncServer(const Params *p) + : SimObject(p), listenEvent(NULL), dataEvent(NULL), number(p->number), + dataFd(-1), _videoWidth(1), _videoHeight(1), clientRfb(0), keyboard(NULL), + mouse(NULL), sendUpdate(false), videoMode(VideoConvert::UnknownMode), + vc(NULL) +{ + if (p->port) + listen(p->port); + + curState = WaitForProtocolVersion; + + + // currently we only support this one pixel format + // unpacked 32bit rgb (rgb888 + 8 bits of nothing/alpha) + // keep it around for telling the client and making + // sure the client cooperates + pixelFormat.bpp = 32; + pixelFormat.depth = 24; + pixelFormat.bigendian = 0; + pixelFormat.truecolor = 1; + pixelFormat.redmax = 0xff; + pixelFormat.greenmax = 0xff; + pixelFormat.bluemax = 0xff; + pixelFormat.redshift = 16; + pixelFormat.greenshift = 8; + pixelFormat.blueshift = 0; + + + DPRINTF(VNC, "Vnc server created at port %d\n", p->port); +} + +VncServer::~VncServer() +{ + if (dataFd != -1) + ::close(dataFd); + + if (listenEvent) + delete listenEvent; + + if (dataEvent) + delete dataEvent; +} + + +//socket creation and vnc client attach +void +VncServer::listen(int port) +{ + if (ListenSocket::allDisabled()) { + warn_once("Sockets disabled, not accepting vnc client connections"); + return; + } + + while (!listener.listen(port, true)) { + DPRINTF(VNC, + "can't bind address vnc server port %d in use PID %d\n", + port, getpid()); + port++; + } + + int p1, p2; + p2 = name().rfind('.') - 1; + p1 = name().rfind('.', p2); + ccprintf(cerr, "Listening for %s connection on port %d\n", + name().substr(p1 + 1, p2 - p1), port); + + listenEvent = new ListenEvent(this, listener.getfd(), POLLIN); + pollQueue.schedule(listenEvent); +} + +// attach a vnc client +void +VncServer::accept() +{ + if (!listener.islistening()) + panic("%s: cannot accept a connection if not listening!", name()); + + int fd = listener.accept(true); + if (dataFd != -1) { + char message[] = "vnc server already attached!\n"; + atomic_write(fd, message, sizeof(message)); + ::close(fd); + return; + } + + dataFd = fd; + + // Send our version number to the client + write((uint8_t*)vncVersion(), strlen(vncVersion())); + + // read the client response + dataEvent = new DataEvent(this, dataFd, POLLIN); + pollQueue.schedule(dataEvent); + + inform("VNC client attached\n"); +} + +// data called by data event +void +VncServer::data() +{ + // We have new data, see if we can handle it + size_t len; + DPRINTF(VNC, "Vnc client message recieved\n"); + + switch (curState) { + case WaitForProtocolVersion: + checkProtocolVersion(); + break; + case WaitForSecurityResponse: + checkSecurity(); + break; + case WaitForClientInit: + // Don't care about shared, just need to read it out of the socket + uint8_t shared; + len = read(&shared); + assert(len == 1); + + // Send our idea of the frame buffer + sendServerInit(); + + break; + case NormalPhase: + uint8_t message_type; + len = read(&message_type); + if (!len) { + detach(); + return; + } + assert(len == 1); + + switch (message_type) { + case ClientSetPixelFormat: + setPixelFormat(); + break; + case ClientSetEncodings: + setEncodings(); + break; + case ClientFrameBufferUpdate: + requestFbUpdate(); + break; + case ClientKeyEvent: + recvKeyboardInput(); + break; + case ClientPointerEvent: + recvPointerInput(); + break; + case ClientCutText: + recvCutText(); + break; + default: + panic("Unimplemented message type recv from client: %d\n", + message_type); + break; + } + break; + default: + panic("Unknown vnc server state\n"); + } +} + + +// read from socket +size_t +VncServer::read(uint8_t *buf, size_t len) +{ + if (dataFd < 0) + panic("vnc not properly attached.\n"); + + size_t ret; + do { + ret = ::read(dataFd, buf, len); + } while (ret == -1 && errno == EINTR); + + + if (ret <= 0){ + DPRINTF(VNC, "Read failed.\n"); + detach(); + return 0; + } + + return ret; +} + +size_t +VncServer::read1(uint8_t *buf, size_t len) +{ + size_t read_len M5_VAR_USED; + read_len = read(buf + 1, len - 1); + assert(read_len == len - 1); + return read_len; +} + + +template +size_t +VncServer::read(T* val) +{ + return read((uint8_t*)val, sizeof(T)); +} + +// write to socket +size_t +VncServer::write(const uint8_t *buf, size_t len) +{ + if (dataFd < 0) + panic("Vnc client not properly attached.\n"); + + ssize_t ret; + ret = atomic_write(dataFd, buf, len); + + if (ret < len) + detach(); + + return ret; +} + +template +size_t +VncServer::write(T* val) +{ + return write((uint8_t*)val, sizeof(T)); +} + +size_t +VncServer::write(const char* str) +{ + return write((uint8_t*)str, strlen(str)); +} + +// detach a vnc client +void +VncServer::detach() +{ + if (dataFd != -1) { + ::close(dataFd); + dataFd = -1; + } + + if (!dataEvent || !dataEvent->queued()) + return; + + pollQueue.remove(dataEvent); + delete dataEvent; + dataEvent = NULL; + curState = WaitForProtocolVersion; + + inform("VNC client detached\n"); + DPRINTF(VNC, "detach vnc client %d\n", number); +} + +void +VncServer::sendError(const char* error_msg) +{ + uint32_t len = strlen(error_msg); + write(&len); + write(error_msg); +} + +void +VncServer::checkProtocolVersion() +{ + assert(curState == WaitForProtocolVersion); + + size_t len M5_VAR_USED; + char version_string[13]; + + // Null terminate the message so it's easier to work with + version_string[12] = 0; + + len = read((uint8_t*)version_string, 12); + assert(len == 12); + + uint32_t major, minor; + + // Figure out the major/minor numbers + if (sscanf(version_string, "RFB %03d.%03d\n", &major, &minor) != 2) { + warn(" Malformed protocol version %s\n", version_string); + sendError("Malformed protocol version\n"); + detach(); + } + + DPRINTF(VNC, "Client request protocol version %d.%d\n", major, minor); + + // If it's not 3.X we don't support it + if (major != 3 || minor < 2) { + warn("Unsupported VNC client version... disconnecting\n"); + uint8_t err = AuthInvalid; + write(&err); + detach(); + } + // Auth is different based on version number + if (minor < 7) { + uint32_t sec_type = htobe((uint32_t)AuthNone); + write(&sec_type); + } else { + uint8_t sec_cnt = 1; + uint8_t sec_type = htobe((uint8_t)AuthNone); + write(&sec_cnt); + write(&sec_type); + } + + // Wait for client to respond + curState = WaitForSecurityResponse; +} + +void +VncServer::checkSecurity() +{ + assert(curState == WaitForSecurityResponse); + + uint8_t security_type; + size_t len M5_VAR_USED = read(&security_type); + + assert(len == 1); + + if (security_type != AuthNone) { + warn("Unknown VNC security type\n"); + sendError("Unknown security type\n"); + } + + DPRINTF(VNC, "Sending security auth OK\n"); + + uint32_t success = htobe(VncOK); + write(&success); + curState = WaitForClientInit; +} + +void +VncServer::sendServerInit() +{ + ServerInitMsg msg; + + DPRINTF(VNC, "Sending server init message to client\n"); + + msg.fbWidth = htobe(videoWidth()); + msg.fbHeight = htobe(videoHeight()); + + msg.px.bpp = htobe(pixelFormat.bpp); + msg.px.depth = htobe(pixelFormat.depth); + msg.px.bigendian = htobe(pixelFormat.bigendian); + msg.px.truecolor = htobe(pixelFormat.truecolor); + msg.px.redmax = htobe(pixelFormat.redmax); + msg.px.greenmax = htobe(pixelFormat.greenmax); + msg.px.bluemax = htobe(pixelFormat.bluemax); + msg.px.redshift = htobe(pixelFormat.redshift); + msg.px.greenshift = htobe(pixelFormat.greenshift); + msg.px.blueshift = htobe(pixelFormat.blueshift); + memset(msg.px.padding, 0, 3); + msg.namelen = 2; + msg.namelen = htobe(msg.namelen); + memcpy(msg.name, "M5", 2); + + write(&msg); + curState = NormalPhase; +} + + +void +VncServer::setPixelFormat() +{ + DPRINTF(VNC, "Received pixel format from client message\n"); + + PixelFormatMessage pfm; + read1((uint8_t*)&pfm, sizeof(PixelFormatMessage)); + + DPRINTF(VNC, " -- bpp = %d; depth = %d; be = %d\n", pfm.px.bpp, + pfm.px.depth, pfm.px.bigendian); + DPRINTF(VNC, " -- true color = %d red,green,blue max = %d,%d,%d\n", + pfm.px.truecolor, betoh(pfm.px.redmax), betoh(pfm.px.greenmax), + betoh(pfm.px.bluemax)); + DPRINTF(VNC, " -- red,green,blue shift = %d,%d,%d\n", pfm.px.redshift, + pfm.px.greenshift, pfm.px.blueshift); + + if (betoh(pfm.px.bpp) != pixelFormat.bpp || + betoh(pfm.px.depth) != pixelFormat.depth || + betoh(pfm.px.bigendian) != pixelFormat.bigendian || + betoh(pfm.px.truecolor) != pixelFormat.truecolor || + betoh(pfm.px.redmax) != pixelFormat.redmax || + betoh(pfm.px.greenmax) != pixelFormat.greenmax || + betoh(pfm.px.bluemax) != pixelFormat.bluemax || + betoh(pfm.px.redshift) != pixelFormat.redshift || + betoh(pfm.px.greenshift) != pixelFormat.greenshift || + betoh(pfm.px.blueshift) != pixelFormat.blueshift) + fatal("VNC client doesn't support true color raw encoding\n"); +} + +void +VncServer::setEncodings() +{ + DPRINTF(VNC, "Received supported encodings from client\n"); + + PixelEncodingsMessage pem; + read1((uint8_t*)&pem, sizeof(PixelEncodingsMessage)); + + pem.num_encodings = betoh(pem.num_encodings); + + DPRINTF(VNC, " -- %d encoding present\n", pem.num_encodings); + supportsRawEnc = supportsResizeEnc = false; + + for (int x = 0; x < pem.num_encodings; x++) { + int32_t encoding; + size_t len M5_VAR_USED; + len = read(&encoding); + assert(len == sizeof(encoding)); + DPRINTF(VNC, " -- supports %d\n", betoh(encoding)); + + switch (betoh(encoding)) { + case EncodingRaw: + supportsRawEnc = true; + break; + case EncodingDesktopSize: + supportsResizeEnc = true; + break; + } + } + + if (!supportsRawEnc) + fatal("VNC clients must always support raw encoding\n"); +} + +void +VncServer::requestFbUpdate() +{ + DPRINTF(VNC, "Received frame buffer update request from client\n"); + + FrameBufferUpdateReq fbr; + read1((uint8_t*)&fbr, sizeof(FrameBufferUpdateReq)); + + fbr.x = betoh(fbr.x); + fbr.y = betoh(fbr.y); + fbr.width = betoh(fbr.width); + fbr.height = betoh(fbr.height); + + DPRINTF(VNC, " -- x = %d y = %d w = %d h = %d\n", fbr.x, fbr.y, fbr.width, + fbr.height); + + sendFrameBufferUpdate(); +} + +void +VncServer::recvKeyboardInput() +{ + DPRINTF(VNC, "Received keyboard input from client\n"); + KeyEventMessage kem; + read1((uint8_t*)&kem, sizeof(KeyEventMessage)); + + kem.key = betoh(kem.key); + DPRINTF(VNC, " -- received key code %d (%s)\n", kem.key, kem.down_flag ? + "down" : "up"); + + if (keyboard) + keyboard->keyPress(kem.key, kem.down_flag); +} + +void +VncServer::recvPointerInput() +{ + DPRINTF(VNC, "Received pointer input from client\n"); + PointerEventMessage pem; + + read1((uint8_t*)&pem, sizeof(PointerEventMessage));; + + pem.x = betoh(pem.x); + pem.y = betoh(pem.y); + DPRINTF(VNC, " -- pointer at x = %d y = %d buttons = %#x\n", pem.x, pem.y, + pem.button_mask); + + if (mouse) + mouse->mouseAt(pem.x, pem.y, pem.button_mask); +} + +void +VncServer::recvCutText() +{ + DPRINTF(VNC, "Received client copy buffer message\n"); + + ClientCutTextMessage cct; + read1((uint8_t*)&cct, sizeof(ClientCutTextMessage)); + + char str[1025]; + size_t data_len = betoh(cct.length); + DPRINTF(VNC, "String length %d\n", data_len); + while (data_len > 0) { + size_t len; + size_t bytes_to_read = data_len > 1024 ? 1024 : data_len; + len = read((uint8_t*)&str, bytes_to_read); + str[bytes_to_read] = 0; + data_len -= len; + assert(data_len >= 0); + DPRINTF(VNC, "Buffer: %s\n", str); + } + +} + + +void +VncServer::sendFrameBufferUpdate() +{ + + if (!clientRfb || dataFd <= 0 || curState != NormalPhase || !sendUpdate) { + DPRINTF(VNC, "NOT sending framebuffer update\n"); + return; + } + + assert(vc); + + // The client will request data constantly, unless we throttle it + sendUpdate = false; + + DPRINTF(VNC, "Sending framebuffer update\n"); + + FrameBufferUpdate fbu; + FrameBufferRect fbr; + + fbu.type = ServerFrameBufferUpdate; + fbu.num_rects = 1; + fbr.x = 0; + fbr.y = 0; + fbr.width = videoWidth(); + fbr.height = videoHeight(); + fbr.encoding = EncodingRaw; + + // fix up endian + fbu.num_rects = htobe(fbu.num_rects); + fbr.x = htobe(fbr.x); + fbr.y = htobe(fbr.y); + fbr.width = htobe(fbr.width); + fbr.height = htobe(fbr.height); + fbr.encoding = htobe(fbr.encoding); + + // send headers to client + write(&fbu); + write(&fbr); + + assert(clientRfb); + + uint8_t *tmp = vc->convert(clientRfb); + write(tmp, videoWidth() * videoHeight() * sizeof(uint32_t)); + delete [] tmp; + +} + +void +VncServer::sendFrameBufferResized() +{ + assert(clientRfb && dataFd > 0 && curState == NormalPhase); + DPRINTF(VNC, "Sending framebuffer resize\n"); + + FrameBufferUpdate fbu; + FrameBufferRect fbr; + + fbu.type = ServerFrameBufferUpdate; + fbu.num_rects = 1; + fbr.x = 0; + fbr.y = 0; + fbr.width = videoWidth(); + fbr.height = videoHeight(); + fbr.encoding = EncodingDesktopSize; + + // fix up endian + fbu.num_rects = htobe(fbu.num_rects); + fbr.x = htobe(fbr.x); + fbr.y = htobe(fbr.y); + fbr.width = htobe(fbr.width); + fbr.height = htobe(fbr.height); + fbr.encoding = htobe(fbr.encoding); + + // send headers to client + write(&fbu); + write(&fbr); + + // No actual data is sent in this message +} + +void +VncServer::setFrameBufferParams(VideoConvert::Mode mode, int width, int height) +{ + DPRINTF(VNC, "Updating video params: mode: %d width: %d height: %d\n", mode, + width, height); + + if (mode != videoMode || width != videoWidth() || height != videoHeight()) { + videoMode = mode; + _videoWidth = width; + _videoHeight = height; + + if (vc) + delete vc; + + vc = new VideoConvert(mode, VideoConvert::rgb8888, videoWidth(), + videoHeight()); + + if (dataFd > 0 && clientRfb && curState == NormalPhase) { + if (supportsResizeEnc) + sendFrameBufferResized(); + else + // The frame buffer changed size and we can't update the client + detach(); + } + } +} + +// create the VNC server object +VncServer * +VncServerParams::create() +{ + return new VncServer(this); +} diff --git a/src/base/vnc/vncserver.hh b/src/base/vnc/vncserver.hh new file mode 100644 index 000000000..23b097b11 --- /dev/null +++ b/src/base/vnc/vncserver.hh @@ -0,0 +1,475 @@ +/* + * Copyright (c) 2010 ARM Limited + * All rights reserved + * + * The license below extends only to copyright in the software and shall + * not be construed as granting a license to any other intellectual + * property including but not limited to intellectual property relating + * to a hardware implementation of the functionality of the software + * licensed hereunder. You may use the software subject to the license + * terms below provided that you ensure that this notice is replicated + * unmodified and in its entirety in all distributions of the software, + * modified or unmodified, in source code or in binary form. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer; + * 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; + * neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * 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. + * + * Authors: Ali Saidi + * William Wang + */ + +/** @file + * Declaration of a VNC server + */ + +#ifndef __DEV_VNC_SERVER_HH__ +#define __DEV_VNC_SERVER_HH__ + +#include + +#include "base/circlebuf.hh" +#include "base/pollevent.hh" +#include "base/socket.hh" +#include "base/vnc/convert.hh" +#include "cpu/intr_control.hh" +#include "sim/sim_object.hh" +#include "params/VncServer.hh" + +/** + * A device that expects to receive input from the vnc server should derrive + * (through mulitple inheritence if necessary from VncKeyboard or VncMouse + * and call setKeyboard() or setMouse() respectively on the vnc server. + */ +class VncKeyboard +{ + public: + /** + * Called when the vnc server receives a key press event from the + * client. + * @param key the key passed is an x11 keysym + * @param down is the key now down or up? + */ + virtual void keyPress(uint32_t key, bool down) = 0; +}; + +class VncMouse +{ + public: + /** + * called whenever the mouse moves or it's button state changes + * buttons is a simple mask with each button (0-8) corresponding to + * a bit position in the byte with 1 being down and 0 being up + * @param x the x position of the mouse + * @param y the y position of the mouse + * @param buttos the button state as described above + */ + virtual void mouseAt(uint16_t x, uint16_t y, uint8_t buttons) = 0; +}; + +class VncServer : public SimObject +{ + public: + + /** + * \defgroup VncConstants A set of constants and structs from the VNC spec + * @{ + */ + /** Authentication modes */ + const static uint32_t AuthInvalid = 0; + const static uint32_t AuthNone = 1; + + /** Error conditions */ + const static uint32_t VncOK = 0; + + /** Client -> Server message IDs */ + enum ClientMessages { + ClientSetPixelFormat = 0, + ClientSetEncodings = 2, + ClientFrameBufferUpdate = 3, + ClientKeyEvent = 4, + ClientPointerEvent = 5, + ClientCutText = 6 + }; + + /** Server -> Client message IDs */ + enum ServerMessages { + ServerFrameBufferUpdate = 0, + ServerSetColorMapEntries = 1, + ServerBell = 2, + ServerCutText = 3 + }; + + /** Encoding types */ + enum EncodingTypes { + EncodingRaw = 0, + EncodingCopyRect = 1, + EncodingHextile = 5, + EncodingDesktopSize = -223 + }; + + /** keyboard/mouse support */ + enum MouseEvents { + MouseLeftButton = 0x1, + MouseRightButton = 0x2, + MouseMiddleButton = 0x4 + }; + + const char* vncVersion() const + { + return "RFB 003.008\n"; + } + + enum ConnectionState { + WaitForProtocolVersion, + WaitForSecurityResponse, + WaitForClientInit, + InitializationPhase, + NormalPhase + }; + + struct PixelFormat { + uint8_t bpp; + uint8_t depth; + uint8_t bigendian; + uint8_t truecolor; + uint16_t redmax; + uint16_t greenmax; + uint16_t bluemax; + uint8_t redshift; + uint8_t greenshift; + uint8_t blueshift; + uint8_t padding[3]; + } M5_ATTR_PACKED; + + struct ServerInitMsg { + uint16_t fbWidth; + uint16_t fbHeight; + PixelFormat px; + uint32_t namelen; + char name[2]; // just to put M5 in here + } M5_ATTR_PACKED; + + struct PixelFormatMessage { + uint8_t type; + uint8_t padding[3]; + PixelFormat px; + } M5_ATTR_PACKED; + + struct PixelEncodingsMessage { + uint8_t type; + uint8_t padding; + uint16_t num_encodings; + } M5_ATTR_PACKED; + + struct FrameBufferUpdateReq { + uint8_t type; + uint8_t incremental; + uint16_t x; + uint16_t y; + uint16_t width; + uint16_t height; + } M5_ATTR_PACKED; + + struct KeyEventMessage { + uint8_t type; + uint8_t down_flag; + uint8_t padding[2]; + uint32_t key; + } M5_ATTR_PACKED; + + struct PointerEventMessage { + uint8_t type; + uint8_t button_mask; + uint16_t x; + uint16_t y; + } M5_ATTR_PACKED; + + struct ClientCutTextMessage { + uint8_t type; + uint8_t padding[3]; + uint32_t length; + } M5_ATTR_PACKED; + + struct FrameBufferUpdate { + uint8_t type; + uint8_t padding; + uint16_t num_rects; + } M5_ATTR_PACKED; + + struct FrameBufferRect { + uint16_t x; + uint16_t y; + uint16_t width; + uint16_t height; + int32_t encoding; + } M5_ATTR_PACKED; + + struct ServerCutText { + uint8_t type; + uint8_t padding[3]; + uint32_t length; + } M5_ATTR_PACKED; + + /** @} */ + + protected: + /** ListenEvent to accept a vnc client connection */ + class ListenEvent: public PollEvent + { + protected: + VncServer *vncserver; + + public: + ListenEvent(VncServer *vs, int fd, int e); + void process(int revent); + }; + + friend class ListenEvent; + ListenEvent *listenEvent; + + /** DataEvent to read data from vnc */ + class DataEvent: public PollEvent + { + protected: + VncServer *vncserver; + + public: + DataEvent(VncServer *vs, int fd, int e); + void process(int revent); + }; + + friend class DataEvent; + DataEvent *dataEvent; + + int number; + int dataFd; // data stream file describer + + ListenSocket listener; + + void listen(int port); + void accept(); + void data(); + void detach(); + + public: + typedef VncServerParams Params; + VncServer(const Params *p); + ~VncServer(); + + // RFB + protected: + + /** The rfb prototol state the connection is in */ + ConnectionState curState; + + /** the width of the frame buffer we are sending to the client */ + uint16_t _videoWidth; + + /** the height of the frame buffer we are sending to the client */ + uint16_t _videoHeight; + + /** pointer to the actual data that is stored in the frame buffer device */ + uint8_t* clientRfb; + + /** The device to notify when we get key events */ + VncKeyboard *keyboard; + + /** The device to notify when we get mouse events */ + VncMouse *mouse; + + /** An update needs to be sent to the client. Without doing this the + * client will constantly request data that is pointless */ + bool sendUpdate; + + /** The one and only pixel format we support */ + PixelFormat pixelFormat; + + /** If the vnc client supports receiving raw data. It always should */ + bool supportsRawEnc; + + /** If the vnc client supports the desktop resize command */ + bool supportsResizeEnc; + + /** The mode of data we're getting frame buffer in */ + VideoConvert::Mode videoMode; + + /** The video converter that transforms data for us */ + VideoConvert *vc; + + protected: + /** + * vnc client Interface + */ + + /** Send an error message to the client + * @param error_msg text to send describing the error + */ + void sendError(const char* error_msg); + + /** Read some data from the client + * @param buf the data to read + * @param len the amount of data to read + * @return length read + */ + size_t read(uint8_t *buf, size_t len); + + /** Read len -1 bytes from the client into the buffer provided + 1 + * assert that we read enough bytes. This function exists to handle + * reading all of the protocol structs above when we've already read + * the first byte which describes which one we're reading + * @param buf the address of the buffer to add one to and read data into + * @param len the amount of data + 1 to read + * @return length read + */ + size_t read1(uint8_t *buf, size_t len); + + + /** Templated version of the read function above to + * read simple data to the client + * @param val data to recv from the client + */ + template size_t read(T* val); + + + /** Write a buffer to the client. + * @param buf buffer to send + * @param len length of the buffer + * @return number of bytes sent + */ + size_t write(const uint8_t *buf, size_t len); + + /** Templated version of the write function above to + * write simple data to the client + * @param val data to send to the client + */ + template size_t write(T* val); + + /** Send a string to the client + * @param str string to transmit + */ + size_t write(const char* str); + + /** Check the client's protocol verion for compatibility and send + * the security types we support + */ + void checkProtocolVersion(); + + /** Check that the security exchange was successful + */ + void checkSecurity(); + + /** Send client our idea about what the frame buffer looks like */ + void sendServerInit(); + + /** Send an error message to the client when something goes wrong + * @param error_msg error to send + */ + void sendError(std::string error_msg); + + /** Send a updated frame buffer to the client. + * @todo this doesn't do anything smart and just sends the entire image + */ + void sendFrameBufferUpdate(); + + /** Receive pixel foramt message from client and process it. */ + void setPixelFormat(); + + /** Receive encodings message from client and process it. */ + void setEncodings(); + + /** Receive message from client asking for updated frame buffer */ + void requestFbUpdate(); + + /** Receive message from client providing new keyboard input */ + void recvKeyboardInput(); + + /** Recv message from client providing new mouse movement or button click */ + void recvPointerInput(); + + /** Receive message from client that there is text in it's paste buffer. + * This is a no-op at the moment, but perhaps we would want to be able to + * paste it at some point. + */ + void recvCutText(); + + /** Tell the client that the frame buffer resized. This happens when the + * simulated system changes video modes (E.g. X11 starts). + */ + void sendFrameBufferResized(); + + public: + /** Set the address of the frame buffer we are going to show. + * To avoid copying, just have the display controller + * tell us where the data is instead of constanly copying it around + * @param rfb frame buffer that we're going to use + */ + void + setFramebufferAddr(uint8_t* rfb) + { + clientRfb = rfb; + } + + /** Set up the device that would like to receive notifications when keys are + * pressed in the vnc client keyboard + * @param _keyboard an object that derrives from VncKeyboard + */ + void setKeyboard(VncKeyboard *_keyboard) { keyboard = _keyboard; } + + /** Setup the device that would like to receive notifications when mouse + * movements or button presses are received from the vnc client. + * @param _mouse an object that derrives from VncMouse + */ + void setMouse(VncMouse *_mouse) { mouse = _mouse; } + + /** The frame buffer uses this call to notify the vnc server that + * the frame buffer has been updated and a new image needs to be sent to the + * client + */ + void + setDirty() + { + sendUpdate = true; + sendFrameBufferUpdate(); + } + + /** What is the width of the screen we're displaying. + * This is used for pointer/tablet devices that need to know to calculate + * the correct value to send to the device driver. + * @return the width of the simulated screen + */ + uint16_t videoWidth() { return _videoWidth; } + + /** What is the height of the screen we're displaying. + * This is used for pointer/tablet devices that need to know to calculate + * the correct value to send to the device driver. + * @return the height of the simulated screen + */ + uint16_t videoHeight() { return _videoHeight; } + + /** Set the mode of the data the frame buffer will be sending us + * @param mode the mode + */ + void setFrameBufferParams(VideoConvert::Mode mode, int width, int height); +}; + +#endif -- cgit v1.2.3