• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /** @file
2 
3   EFI_GRAPHICS_OUTPUT_PROTOCOL member functions for the VirtIo GPU driver.
4 
5   Copyright (C) 2016, Red Hat, Inc.
6 
7   This program and the accompanying materials are licensed and made available
8   under the terms and conditions of the BSD License which accompanies this
9   distribution. The full text of the license may be found at
10   http://opensource.org/licenses/bsd-license.php
11 
12   THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, WITHOUT
13   WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
14 
15 **/
16 
17 #include <Library/BaseMemoryLib.h>
18 #include <Library/MemoryAllocationLib.h>
19 
20 #include "VirtioGpu.h"
21 
22 /**
23   Release guest-side and host-side resources that are related to an initialized
24   VGPU_GOP.Gop.
25 
26   param[in,out] VgpuGop  The VGPU_GOP object to release resources for.
27 
28                          On input, the caller is responsible for having called
29                          VgpuGop->Gop.SetMode() at least once successfully.
30                          (This is equivalent to the requirement that
31                          VgpuGop->BackingStore be non-NULL. It is also
32                          equivalent to the requirement that VgpuGop->ResourceId
33                          be nonzero.)
34 
35                          On output, resources will be released, and
36                          VgpuGop->BackingStore and VgpuGop->ResourceId will be
37                          nulled.
38 
39   param[in] DisableHead  Whether this head (scanout) currently references the
40                          resource identified by VgpuGop->ResourceId. Only pass
41                          FALSE when VgpuGop->Gop.SetMode() calls this function
42                          while switching between modes, and set it to TRUE
43                          every other time.
44 **/
45 VOID
ReleaseGopResources(IN OUT VGPU_GOP * VgpuGop,IN BOOLEAN DisableHead)46 ReleaseGopResources (
47   IN OUT VGPU_GOP *VgpuGop,
48   IN     BOOLEAN  DisableHead
49   )
50 {
51   EFI_STATUS Status;
52 
53   ASSERT (VgpuGop->ResourceId != 0);
54   ASSERT (VgpuGop->BackingStore != NULL);
55 
56   //
57   // If any of the following host-side destruction steps fail, we can't get out
58   // of an inconsistent state, so we'll hang. In general errors in object
59   // destruction can hardly be recovered from.
60   //
61   if (DisableHead) {
62     //
63     // Dissociate head (scanout) #0 from the currently used 2D host resource,
64     // by setting ResourceId=0 for it.
65     //
66     Status = VirtioGpuSetScanout (
67                VgpuGop->ParentBus, // VgpuDev
68                0, 0, 0, 0,         // X, Y, Width, Height
69                0,                  // ScanoutId
70                0                   // ResourceId
71                );
72     //
73     // HACK BEGINS HERE
74     //
75     // According to the GPU Device section of the VirtIo specification, the
76     // above operation is valid:
77     //
78     // "The driver can use resource_id = 0 to disable a scanout."
79     //
80     // However, in practice QEMU does not allow us to disable head (scanout) #0
81     // -- it rejects the command with response code 0x1202
82     // (VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID). Looking at the QEMU source
83     // code, function virtio_gpu_set_scanout() in "hw/display/virtio-gpu.c",
84     // this appears fully intentional, despite not being documented in the
85     // spec.
86     //
87     // Surprisingly, ignoring the error here, and proceeding to release
88     // host-side resources that presumably underlie head (scanout) #0, work
89     // without any problems -- the driver survives repeated "disconnect" /
90     // "connect -r" commands in the UEFI shell.
91     //
92     // So, for now, let's just suppress the error.
93     //
94     Status = EFI_SUCCESS;
95     //
96     // HACK ENDS HERE
97     //
98 
99     ASSERT_EFI_ERROR (Status);
100     if (EFI_ERROR (Status)) {
101       CpuDeadLoop ();
102     }
103   }
104 
105   //
106   // Detach backing pages from the currently used 2D host resource.
107   //
108   Status = VirtioGpuResourceDetachBacking (
109              VgpuGop->ParentBus, // VgpuDev
110              VgpuGop->ResourceId // ResourceId
111              );
112   ASSERT_EFI_ERROR (Status);
113   if (EFI_ERROR (Status)) {
114     CpuDeadLoop ();
115   }
116 
117   //
118   // Release backing pages.
119   //
120   FreePages (VgpuGop->BackingStore, VgpuGop->NumberOfPages);
121   VgpuGop->BackingStore  = NULL;
122   VgpuGop->NumberOfPages = 0;
123 
124   //
125   // Destroy the currently used 2D host resource.
126   //
127   Status = VirtioGpuResourceUnref (
128              VgpuGop->ParentBus, // VgpuDev
129              VgpuGop->ResourceId // ResourceId
130              );
131   ASSERT_EFI_ERROR (Status);
132   if (EFI_ERROR (Status)) {
133     CpuDeadLoop ();
134   }
135   VgpuGop->ResourceId = 0;
136 }
137 
138 //
139 // The resolutions supported by this driver.
140 //
141 typedef struct {
142   UINT32 Width;
143   UINT32 Height;
144 } GOP_RESOLUTION;
145 
146 STATIC CONST GOP_RESOLUTION mGopResolutions[] = {
147   {  640,  480 },
148   {  800,  480 },
149   {  800,  600 },
150   {  832,  624 },
151   {  960,  640 },
152   { 1024,  600 },
153   { 1024,  768 },
154   { 1152,  864 },
155   { 1152,  870 },
156   { 1280,  720 },
157   { 1280,  760 },
158   { 1280,  768 },
159   { 1280,  800 },
160   { 1280,  960 },
161   { 1280, 1024 },
162   { 1360,  768 },
163   { 1366,  768 },
164   { 1400, 1050 },
165   { 1440,  900 },
166   { 1600,  900 },
167   { 1600, 1200 },
168   { 1680, 1050 },
169   { 1920, 1080 },
170   { 1920, 1200 },
171   { 1920, 1440 },
172   { 2000, 2000 },
173   { 2048, 1536 },
174   { 2048, 2048 },
175   { 2560, 1440 },
176   { 2560, 1600 },
177   { 2560, 2048 },
178   { 2800, 2100 },
179   { 3200, 2400 },
180   { 3840, 2160 },
181   { 4096, 2160 },
182   { 7680, 4320 },
183   { 8192, 4320 },
184 };
185 
186 //
187 // Macro for casting VGPU_GOP.Gop to VGPU_GOP.
188 //
189 #define VGPU_GOP_FROM_GOP(GopPointer) \
190           CR (GopPointer, VGPU_GOP, Gop, VGPU_GOP_SIG)
191 
192 //
193 // EFI_GRAPHICS_OUTPUT_PROTOCOL member functions.
194 //
195 STATIC
196 EFI_STATUS
197 EFIAPI
GopQueryMode(IN EFI_GRAPHICS_OUTPUT_PROTOCOL * This,IN UINT32 ModeNumber,OUT UINTN * SizeOfInfo,OUT EFI_GRAPHICS_OUTPUT_MODE_INFORMATION ** Info)198 GopQueryMode (
199   IN  EFI_GRAPHICS_OUTPUT_PROTOCOL         *This,
200   IN  UINT32                               ModeNumber,
201   OUT UINTN                                *SizeOfInfo,
202   OUT EFI_GRAPHICS_OUTPUT_MODE_INFORMATION **Info
203   )
204 {
205   EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *GopModeInfo;
206 
207   if (ModeNumber >= ARRAY_SIZE (mGopResolutions)) {
208     return EFI_INVALID_PARAMETER;
209   }
210 
211   GopModeInfo = AllocateZeroPool (sizeof *GopModeInfo);
212   if (GopModeInfo == NULL) {
213     return EFI_OUT_OF_RESOURCES;
214   }
215 
216   GopModeInfo->HorizontalResolution = mGopResolutions[ModeNumber].Width;
217   GopModeInfo->VerticalResolution   = mGopResolutions[ModeNumber].Height;
218   GopModeInfo->PixelFormat          = PixelBltOnly;
219   GopModeInfo->PixelsPerScanLine    = mGopResolutions[ModeNumber].Width;
220 
221   *SizeOfInfo = sizeof *GopModeInfo;
222   *Info = GopModeInfo;
223   return EFI_SUCCESS;
224 }
225 
226 STATIC
227 EFI_STATUS
228 EFIAPI
GopSetMode(IN EFI_GRAPHICS_OUTPUT_PROTOCOL * This,IN UINT32 ModeNumber)229 GopSetMode (
230   IN  EFI_GRAPHICS_OUTPUT_PROTOCOL *This,
231   IN  UINT32                       ModeNumber
232   )
233 {
234   VGPU_GOP   *VgpuGop;
235   UINT32     NewResourceId;
236   UINTN      NewNumberOfBytes;
237   UINTN      NewNumberOfPages;
238   VOID       *NewBackingStore;
239   EFI_STATUS Status;
240   EFI_STATUS Status2;
241 
242   if (ModeNumber >= ARRAY_SIZE (mGopResolutions)) {
243     return EFI_UNSUPPORTED;
244   }
245 
246   VgpuGop = VGPU_GOP_FROM_GOP (This);
247 
248   //
249   // Distinguish the first (internal) call from the other (protocol consumer)
250   // calls.
251   //
252   if (VgpuGop->ResourceId == 0) {
253     //
254     // Set up the Gop -> GopMode -> GopModeInfo pointer chain, and the other
255     // (nonzero) constant fields.
256     //
257     // No direct framebuffer access is supported, only Blt() is.
258     //
259     VgpuGop->Gop.Mode = &VgpuGop->GopMode;
260 
261     VgpuGop->GopMode.MaxMode         = (UINT32)(ARRAY_SIZE (mGopResolutions));
262     VgpuGop->GopMode.Info            = &VgpuGop->GopModeInfo;
263     VgpuGop->GopMode.SizeOfInfo      = sizeof VgpuGop->GopModeInfo;
264 
265     VgpuGop->GopModeInfo.PixelFormat = PixelBltOnly;
266 
267     //
268     // This is the first time we create a host side resource.
269     //
270     NewResourceId = 1;
271   } else {
272     //
273     // We already have an active host side resource. Create the new one without
274     // interfering with the current one, so that we can cleanly bail out on
275     // error, without disturbing the current graphics mode.
276     //
277     // The formula below will alternate between IDs 1 and 2.
278     //
279     NewResourceId = 3 - VgpuGop->ResourceId;
280   }
281 
282   //
283   // Create the 2D host resource.
284   //
285   Status = VirtioGpuResourceCreate2d (
286              VgpuGop->ParentBus,                // VgpuDev
287              NewResourceId,                     // ResourceId
288              VirtioGpuFormatB8G8R8X8Unorm,      // Format
289              mGopResolutions[ModeNumber].Width, // Width
290              mGopResolutions[ModeNumber].Height // Height
291              );
292   if (EFI_ERROR (Status)) {
293     return Status;
294   }
295 
296   //
297   // Allocate guest backing store.
298   //
299   NewNumberOfBytes = mGopResolutions[ModeNumber].Width *
300                      mGopResolutions[ModeNumber].Height * sizeof (UINT32);
301   NewNumberOfPages = EFI_SIZE_TO_PAGES (NewNumberOfBytes);
302   NewBackingStore = AllocatePages (NewNumberOfPages);
303   if (NewBackingStore == NULL) {
304     Status = EFI_OUT_OF_RESOURCES;
305     goto DestroyHostResource;
306   }
307   //
308   // Fill visible part of backing store with black.
309   //
310   ZeroMem (NewBackingStore, NewNumberOfBytes);
311 
312   //
313   // Attach backing store to the host resource.
314   //
315   Status = VirtioGpuResourceAttachBacking (
316              VgpuGop->ParentBus, // VgpuDev
317              NewResourceId,      // ResourceId
318              NewBackingStore,    // FirstBackingPage
319              NewNumberOfPages    // NumberOfPages
320              );
321   if (EFI_ERROR (Status)) {
322     goto FreeBackingStore;
323   }
324 
325   //
326   // Point head (scanout) #0 to the host resource.
327   //
328   Status = VirtioGpuSetScanout (
329              VgpuGop->ParentBus,                 // VgpuDev
330              0,                                  // X
331              0,                                  // Y
332              mGopResolutions[ModeNumber].Width,  // Width
333              mGopResolutions[ModeNumber].Height, // Height
334              0,                                  // ScanoutId
335              NewResourceId                       // ResourceId
336              );
337   if (EFI_ERROR (Status)) {
338     goto DetachBackingStore;
339   }
340 
341   //
342   // If this is not the first (i.e., internal) call, then we have to (a) flush
343   // the new resource to head (scanout) #0, after having flipped the latter to
344   // the former above, plus (b) release the old resources.
345   //
346   if (VgpuGop->ResourceId != 0) {
347     Status = VirtioGpuResourceFlush (
348                VgpuGop->ParentBus,                 // VgpuDev
349                0,                                  // X
350                0,                                  // Y
351                mGopResolutions[ModeNumber].Width,  // Width
352                mGopResolutions[ModeNumber].Height, // Height
353                NewResourceId                       // ResourceId
354                );
355     if (EFI_ERROR (Status)) {
356       //
357       // Flip head (scanout) #0 back to the current resource. If this fails, we
358       // cannot continue, as this error occurs on the error path and is
359       // therefore non-recoverable.
360       //
361       Status2 = VirtioGpuSetScanout (
362                   VgpuGop->ParentBus,                       // VgpuDev
363                   0,                                        // X
364                   0,                                        // Y
365                   mGopResolutions[This->Mode->Mode].Width,  // Width
366                   mGopResolutions[This->Mode->Mode].Height, // Height
367                   0,                                        // ScanoutId
368                   VgpuGop->ResourceId                       // ResourceId
369                   );
370       ASSERT_EFI_ERROR (Status2);
371       if (EFI_ERROR (Status2)) {
372         CpuDeadLoop ();
373       }
374       goto DetachBackingStore;
375     }
376 
377     //
378     // Flush successful; release the old resources (without disabling head
379     // (scanout) #0).
380     //
381     ReleaseGopResources (VgpuGop, FALSE /* DisableHead */);
382   }
383 
384   //
385   // This is either the first (internal) call when we have no old resources
386   // yet, or we've changed the mode successfully and released the old
387   // resources.
388   //
389   ASSERT (VgpuGop->ResourceId == 0);
390   ASSERT (VgpuGop->BackingStore == NULL);
391 
392   VgpuGop->ResourceId = NewResourceId;
393   VgpuGop->BackingStore = NewBackingStore;
394   VgpuGop->NumberOfPages = NewNumberOfPages;
395 
396   //
397   // Populate Mode and ModeInfo (mutable fields only).
398   //
399   VgpuGop->GopMode.Mode = ModeNumber;
400   VgpuGop->GopModeInfo.HorizontalResolution =
401                                              mGopResolutions[ModeNumber].Width;
402   VgpuGop->GopModeInfo.VerticalResolution = mGopResolutions[ModeNumber].Height;
403   VgpuGop->GopModeInfo.PixelsPerScanLine = mGopResolutions[ModeNumber].Width;
404   return EFI_SUCCESS;
405 
406 DetachBackingStore:
407   Status2 = VirtioGpuResourceDetachBacking (VgpuGop->ParentBus, NewResourceId);
408   ASSERT_EFI_ERROR (Status2);
409   if (EFI_ERROR (Status2)) {
410     CpuDeadLoop ();
411   }
412 
413 FreeBackingStore:
414   FreePages (NewBackingStore, NewNumberOfPages);
415 
416 DestroyHostResource:
417   Status2 = VirtioGpuResourceUnref (VgpuGop->ParentBus, NewResourceId);
418   ASSERT_EFI_ERROR (Status2);
419   if (EFI_ERROR (Status2)) {
420     CpuDeadLoop ();
421   }
422 
423   return Status;
424 }
425 
426 STATIC
427 EFI_STATUS
428 EFIAPI
GopBlt(IN EFI_GRAPHICS_OUTPUT_PROTOCOL * This,IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL * BltBuffer,OPTIONAL IN EFI_GRAPHICS_OUTPUT_BLT_OPERATION BltOperation,IN UINTN SourceX,IN UINTN SourceY,IN UINTN DestinationX,IN UINTN DestinationY,IN UINTN Width,IN UINTN Height,IN UINTN Delta OPTIONAL)429 GopBlt (
430   IN  EFI_GRAPHICS_OUTPUT_PROTOCOL      *This,
431   IN  EFI_GRAPHICS_OUTPUT_BLT_PIXEL     *BltBuffer,   OPTIONAL
432   IN  EFI_GRAPHICS_OUTPUT_BLT_OPERATION BltOperation,
433   IN  UINTN                             SourceX,
434   IN  UINTN                             SourceY,
435   IN  UINTN                             DestinationX,
436   IN  UINTN                             DestinationY,
437   IN  UINTN                             Width,
438   IN  UINTN                             Height,
439   IN  UINTN                             Delta         OPTIONAL
440   )
441 {
442   VGPU_GOP   *VgpuGop;
443   UINT32     CurrentHorizontal;
444   UINT32     CurrentVertical;
445   UINTN      SegmentSize;
446   UINTN      Y;
447   UINTN      ResourceOffset;
448   EFI_STATUS Status;
449 
450   VgpuGop = VGPU_GOP_FROM_GOP (This);
451   CurrentHorizontal = VgpuGop->GopModeInfo.HorizontalResolution;
452   CurrentVertical   = VgpuGop->GopModeInfo.VerticalResolution;
453 
454   //
455   // We can avoid pixel format conversion in the guest because the internal
456   // representation of EFI_GRAPHICS_OUTPUT_BLT_PIXEL and that of
457   // VirtioGpuFormatB8G8R8X8Unorm are identical.
458   //
459   SegmentSize = Width * sizeof (UINT32);
460 
461   //
462   // Delta is relevant for operations that read a rectangle from, or write a
463   // rectangle to, BltBuffer.
464   //
465   // In these cases, Delta is the stride of BltBuffer, in bytes. If Delta is
466   // zero, then Width is the entire width of BltBuffer, and the stride is
467   // supposed to be calculated from Width.
468   //
469   if (BltOperation == EfiBltVideoToBltBuffer ||
470       BltOperation == EfiBltBufferToVideo) {
471     if (Delta == 0) {
472       Delta = SegmentSize;
473     }
474   }
475 
476   //
477   // For operations that write to the display, check if the destination fits
478   // onto the display.
479   //
480   if (BltOperation == EfiBltVideoFill ||
481       BltOperation == EfiBltBufferToVideo ||
482       BltOperation == EfiBltVideoToVideo) {
483     if (DestinationX > CurrentHorizontal ||
484         Width > CurrentHorizontal - DestinationX ||
485         DestinationY > CurrentVertical ||
486         Height > CurrentVertical - DestinationY) {
487       return EFI_INVALID_PARAMETER;
488     }
489   }
490 
491   //
492   // For operations that read from the display, check if the source fits onto
493   // the display.
494   //
495   if (BltOperation == EfiBltVideoToBltBuffer ||
496       BltOperation == EfiBltVideoToVideo) {
497     if (SourceX > CurrentHorizontal ||
498         Width > CurrentHorizontal - SourceX ||
499         SourceY > CurrentVertical ||
500         Height > CurrentVertical - SourceY) {
501       return EFI_INVALID_PARAMETER;
502     }
503   }
504 
505   //
506   // Render the request. For requests that do not modify the display, there
507   // won't be further steps.
508   //
509   switch (BltOperation) {
510   case EfiBltVideoFill:
511     //
512     // Write data from the BltBuffer pixel (0, 0) directly to every pixel of
513     // the video display rectangle (DestinationX, DestinationY) (DestinationX +
514     // Width, DestinationY + Height). Only one pixel will be used from the
515     // BltBuffer. Delta is NOT used.
516     //
517     for (Y = 0; Y < Height; ++Y) {
518       SetMem32 (
519         VgpuGop->BackingStore +
520           (DestinationY + Y) * CurrentHorizontal + DestinationX,
521         SegmentSize,
522         *(UINT32 *)BltBuffer
523         );
524     }
525     break;
526 
527   case EfiBltVideoToBltBuffer:
528     //
529     // Read data from the video display rectangle (SourceX, SourceY) (SourceX +
530     // Width, SourceY + Height) and place it in the BltBuffer rectangle
531     // (DestinationX, DestinationY ) (DestinationX + Width, DestinationY +
532     // Height). If DestinationX or DestinationY is not zero then Delta must be
533     // set to the length in bytes of a row in the BltBuffer.
534     //
535     for (Y = 0; Y < Height; ++Y) {
536       CopyMem (
537         (UINT8 *)BltBuffer +
538           (DestinationY + Y) * Delta + DestinationX * sizeof *BltBuffer,
539         VgpuGop->BackingStore +
540           (SourceY + Y) * CurrentHorizontal + SourceX,
541         SegmentSize
542         );
543     }
544     return EFI_SUCCESS;
545 
546   case EfiBltBufferToVideo:
547     //
548     // Write data from the BltBuffer rectangle (SourceX, SourceY) (SourceX +
549     // Width, SourceY + Height) directly to the video display rectangle
550     // (DestinationX, DestinationY) (DestinationX + Width, DestinationY +
551     // Height). If SourceX or SourceY is not zero then Delta must be set to the
552     // length in bytes of a row in the BltBuffer.
553     //
554     for (Y = 0; Y < Height; ++Y) {
555       CopyMem (
556         VgpuGop->BackingStore +
557           (DestinationY + Y) * CurrentHorizontal + DestinationX,
558         (UINT8 *)BltBuffer +
559           (SourceY + Y) * Delta + SourceX * sizeof *BltBuffer,
560         SegmentSize
561         );
562     }
563     break;
564 
565   case EfiBltVideoToVideo:
566     //
567     // Copy from the video display rectangle (SourceX, SourceY) (SourceX +
568     // Width, SourceY + Height) to the video display rectangle (DestinationX,
569     // DestinationY) (DestinationX + Width, DestinationY + Height). The
570     // BltBuffer and Delta are not used in this mode.
571     //
572     // A single invocation of CopyMem() handles overlap between source and
573     // destination (that is, within a single line), but for multiple
574     // invocations, we must handle overlaps.
575     //
576     if (SourceY < DestinationY) {
577       Y = Height;
578       while (Y > 0) {
579         --Y;
580         CopyMem (
581           VgpuGop->BackingStore +
582             (DestinationY + Y) * CurrentHorizontal + DestinationX,
583           VgpuGop->BackingStore +
584             (SourceY + Y) * CurrentHorizontal + SourceX,
585           SegmentSize
586           );
587       }
588     } else {
589       for (Y = 0; Y < Height; ++Y) {
590         CopyMem (
591           VgpuGop->BackingStore +
592             (DestinationY + Y) * CurrentHorizontal + DestinationX,
593           VgpuGop->BackingStore +
594             (SourceY + Y) * CurrentHorizontal + SourceX,
595           SegmentSize
596           );
597       }
598     }
599     break;
600 
601   default:
602     return EFI_INVALID_PARAMETER;
603   }
604 
605   //
606   // For operations that wrote to the display, submit the updated area to the
607   // host -- update the host resource from guest memory.
608   //
609   ResourceOffset = sizeof (UINT32) * (DestinationY * CurrentHorizontal +
610                                       DestinationX);
611   Status = VirtioGpuTransferToHost2d (
612              VgpuGop->ParentBus,   // VgpuDev
613              (UINT32)DestinationX, // X
614              (UINT32)DestinationY, // Y
615              (UINT32)Width,        // Width
616              (UINT32)Height,       // Height
617              ResourceOffset,       // Offset
618              VgpuGop->ResourceId   // ResourceId
619              );
620   if (EFI_ERROR (Status)) {
621     return Status;
622   }
623 
624   //
625   // Flush the updated resource to the display.
626   //
627   Status = VirtioGpuResourceFlush (
628              VgpuGop->ParentBus,   // VgpuDev
629              (UINT32)DestinationX, // X
630              (UINT32)DestinationY, // Y
631              (UINT32)Width,        // Width
632              (UINT32)Height,       // Height
633              VgpuGop->ResourceId   // ResourceId
634              );
635   return Status;
636 }
637 
638 //
639 // Template for initializing VGPU_GOP.Gop.
640 //
641 CONST EFI_GRAPHICS_OUTPUT_PROTOCOL mGopTemplate = {
642   GopQueryMode,
643   GopSetMode,
644   GopBlt,
645   NULL          // Mode, to be overwritten in the actual protocol instance
646 };
647