xref: /openbmc/obmc-ikvm/ikvm_server.cpp (revision 3c110335)
1 #include "ikvm_server.hpp"
2 
3 #include <linux/videodev2.h>
4 #include <rfb/rfbproto.h>
5 
6 #include <boost/crc.hpp>
7 #include <phosphor-logging/elog-errors.hpp>
8 #include <phosphor-logging/elog.hpp>
9 #include <phosphor-logging/log.hpp>
10 #include <xyz/openbmc_project/Common/error.hpp>
11 
12 namespace ikvm
13 {
14 
15 using namespace phosphor::logging;
16 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
17 
Server(const Args & args,Input & i,Video & v)18 Server::Server(const Args& args, Input& i, Video& v) :
19     pendingResize(false), frameCounter(0), numClients(0), input(i), video(v)
20 {
21     std::string ip("localhost");
22     const Args::CommandLine& commandLine = args.getCommandLine();
23     int argc = commandLine.argc;
24 
25     server = rfbGetScreen(&argc, commandLine.argv, video.getWidth(),
26                           video.getHeight(), Video::bitsPerSample,
27                           Video::samplesPerPixel, Video::bytesPerPixel);
28 
29     if (!server)
30     {
31         log<level::ERR>("Failed to get VNC screen due to invalid arguments");
32         elog<InvalidArgument>(
33             xyz::openbmc_project::Common::InvalidArgument::ARGUMENT_NAME(""),
34             xyz::openbmc_project::Common::InvalidArgument::ARGUMENT_VALUE(""));
35     }
36 
37     framebuffer.resize(
38         video.getHeight() * video.getWidth() * Video::bytesPerPixel, 0);
39 
40     server->screenData = this;
41     server->desktopName = "OpenBMC IKVM";
42     server->frameBuffer = framebuffer.data();
43     server->newClientHook = newClient;
44     server->cursor = rfbMakeXCursor(cursorWidth, cursorHeight, (char*)cursor,
45                                     (char*)cursorMask);
46     server->cursor->xhot = 1;
47     server->cursor->yhot = 1;
48 
49     rfbStringToAddr(&ip[0], &server->listenInterface);
50 
51     rfbInitServer(server);
52 
53     rfbMarkRectAsModified(server, 0, 0, video.getWidth(), video.getHeight());
54 
55     server->kbdAddEvent = Input::keyEvent;
56     server->ptrAddEvent = Input::pointerEvent;
57 
58     processTime = (1000000 / video.getFrameRate()) - 100;
59 
60     calcFrameCRC = args.getCalcFrameCRC();
61 }
62 
~Server()63 Server::~Server()
64 {
65     rfbScreenCleanup(server);
66 }
67 
resize()68 void Server::resize()
69 {
70     if (frameCounter > video.getFrameRate())
71     {
72         doResize();
73     }
74     else
75     {
76         pendingResize = true;
77     }
78 }
79 
run()80 void Server::run()
81 {
82     rfbProcessEvents(server, processTime);
83 
84     if (server->clientHead)
85     {
86         frameCounter++;
87         if (pendingResize && frameCounter > video.getFrameRate())
88         {
89             doResize();
90             pendingResize = false;
91         }
92     }
93 }
94 
sendFrame()95 void Server::sendFrame()
96 {
97     char* data = video.getData();
98     rfbClientIteratorPtr it;
99     rfbClientPtr cl;
100     int64_t frame_crc = -1;
101 
102     if (!data || pendingResize)
103     {
104         return;
105     }
106 
107     it = rfbGetClientIterator(server);
108 
109     while ((cl = rfbClientIteratorNext(it)))
110     {
111         ClientData* cd = (ClientData*)cl->clientData;
112         rfbFramebufferUpdateMsg* fu = (rfbFramebufferUpdateMsg*)cl->updateBuf;
113 
114         if (!cd)
115         {
116             continue;
117         }
118 
119         if (cd->skipFrame)
120         {
121             cd->skipFrame--;
122             continue;
123         }
124 
125         if (!cd->needUpdate)
126         {
127             continue;
128         }
129 
130         if (calcFrameCRC)
131         {
132             if (frame_crc == -1)
133             {
134                 /* JFIF header contains some varying data so skip it for
135                  * checksum calculation */
136                 frame_crc = boost::crc<32, 0x04C11DB7, 0xFFFFFFFF, 0xFFFFFFFF,
137                                        true, true>(data + 0x30,
138                                                    video.getFrameSize() - 0x30);
139             }
140 
141             if (cd->last_crc == frame_crc)
142             {
143                 continue;
144             }
145 
146             cd->last_crc = frame_crc;
147         }
148 
149         cd->needUpdate = false;
150 
151         if (cl->enableLastRectEncoding)
152         {
153             fu->nRects = 0xFFFF;
154         }
155         else
156         {
157             fu->nRects = Swap16IfLE(1);
158         }
159 
160         switch (video.getPixelformat())
161         {
162             case V4L2_PIX_FMT_RGB24:
163                 framebuffer.assign(data, data + video.getFrameSize());
164                 rfbMarkRectAsModified(server, 0, 0, video.getWidth(),
165                                       video.getHeight());
166                 break;
167 
168             case V4L2_PIX_FMT_JPEG:
169                 fu->type = rfbFramebufferUpdate;
170                 cl->ublen = sz_rfbFramebufferUpdateMsg;
171                 rfbSendUpdateBuf(cl);
172                 cl->tightEncoding = rfbEncodingTight;
173                 rfbSendTightHeader(cl, 0, 0, video.getWidth(),
174                                    video.getHeight());
175                 cl->updateBuf[cl->ublen++] = (char)(rfbTightJpeg << 4);
176                 rfbSendCompressedDataTight(cl, data, video.getFrameSize());
177                 if (cl->enableLastRectEncoding)
178                 {
179                     rfbSendLastRectMarker(cl);
180                 }
181                 rfbSendUpdateBuf(cl);
182                 break;
183 
184             default:
185                 break;
186         }
187     }
188 
189     rfbReleaseClientIterator(it);
190 }
191 
clientFramebufferUpdateRequest(rfbClientPtr cl,rfbFramebufferUpdateRequestMsg * furMsg)192 void Server::clientFramebufferUpdateRequest(
193     rfbClientPtr cl, rfbFramebufferUpdateRequestMsg* furMsg)
194 {
195     ClientData* cd = (ClientData*)cl->clientData;
196 
197     if (!cd)
198         return;
199 
200     // Ignore the furMsg info. This service uses full frame update always.
201     (void)furMsg;
202 
203     cd->needUpdate = true;
204 }
205 
clientGone(rfbClientPtr cl)206 void Server::clientGone(rfbClientPtr cl)
207 {
208     Server* server = (Server*)cl->screen->screenData;
209 
210     delete (ClientData*)cl->clientData;
211     cl->clientData = nullptr;
212 
213     if (server->numClients-- == 1)
214     {
215         server->input.disconnect();
216         rfbMarkRectAsModified(server->server, 0, 0, server->video.getWidth(),
217                               server->video.getHeight());
218     }
219 }
220 
newClient(rfbClientPtr cl)221 enum rfbNewClientAction Server::newClient(rfbClientPtr cl)
222 {
223     Server* server = (Server*)cl->screen->screenData;
224 
225     cl->clientData = new ClientData(server->video.getFrameRate(),
226                                     &server->input);
227     cl->clientGoneHook = clientGone;
228     cl->clientFramebufferUpdateRequestHook = clientFramebufferUpdateRequest;
229     if (!server->numClients++)
230     {
231         server->input.connect();
232         server->pendingResize = false;
233         server->frameCounter = 0;
234     }
235 
236     return RFB_CLIENT_ACCEPT;
237 }
238 
doResize()239 void Server::doResize()
240 {
241     rfbClientIteratorPtr it;
242     rfbClientPtr cl;
243 
244     framebuffer.resize(
245         video.getHeight() * video.getWidth() * Video::bytesPerPixel, 0);
246 
247     rfbNewFramebuffer(server, framebuffer.data(), video.getWidth(),
248                       video.getHeight(), Video::bitsPerSample,
249                       Video::samplesPerPixel, Video::bytesPerPixel);
250     rfbMarkRectAsModified(server, 0, 0, video.getWidth(), video.getHeight());
251 
252     it = rfbGetClientIterator(server);
253 
254     while ((cl = rfbClientIteratorNext(it)))
255     {
256         ClientData* cd = (ClientData*)cl->clientData;
257 
258         if (!cd)
259         {
260             continue;
261         }
262 
263         // delay video updates to give the client time to resize
264         cd->skipFrame = video.getFrameRate();
265     }
266 
267     rfbReleaseClientIterator(it);
268 }
269 
270 } // namespace ikvm
271