summaryrefslogtreecommitdiffstats
path: root/MdeModulePkg/Library/DisplayUpdateProgressLibGraphics/DisplayUpdateProgressLibGraphics.c
blob: 5f3ae6ab72ca9b0fd7344776d4fafc04703ff9ae (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
/** @file
  Provides services to display completion progress of a firmware update on a
  graphical console that supports the Graphics Output Protocol.

  Copyright (c) 2016, Microsoft Corporation. All rights reserved.<BR>
  Copyright (c) 2018, Intel Corporation. All rights reserved.<BR>

  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include <PiDxe.h>
#include <Library/BaseMemoryLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/DebugLib.h>
#include <Library/BaseLib.h>
#include <Library/UefiLib.h>

#include <Protocol/GraphicsOutput.h>
#include <Protocol/BootLogo2.h>

//
// Values in percent of of logo height.
//
#define LOGO_BOTTOM_PADDING    20
#define PROGRESS_BLOCK_HEIGHT  10

//
// Graphics Output Protocol instance to display progress bar
//
EFI_GRAPHICS_OUTPUT_PROTOCOL  *mGop = NULL;

//
// Set to 100 percent so it is reset on first call.
//
UINTN mPreviousProgress = 100;

//
// Display coordinates for the progress bar.
//
UINTN  mStartX = 0;
UINTN  mStartY = 0;

//
// Width and height of the progress bar.
//
UINTN  mBlockWidth  = 0;
UINTN  mBlockHeight = 0;

//
// GOP bitmap of the progress bar. Initialized on every new progress of 100%
//
EFI_GRAPHICS_OUTPUT_BLT_PIXEL  *mBlockBitmap;

//
// GOP bitmap of the progress bar backround.  Initialized once.
//
EFI_GRAPHICS_OUTPUT_BLT_PIXEL  *mProgressBarBackground;

//
// Default mask used to detect the left, right , top, and bottom of logo.  Only
// green and blue pixels are used for logo detection.
//
const EFI_GRAPHICS_OUTPUT_BLT_PIXEL_UNION  mLogoDetectionColorMask = {
  {
    0xFF,  // Blue
    0xFF,  // Green
    0x00,  // Red
    0x00   // Reserved
  }
};

//
// Background color of progress bar.  Grey.
//
const EFI_GRAPHICS_OUTPUT_BLT_PIXEL_UNION  mProgressBarBackgroundColor = {
  {
    0x80,  // Blue
    0x80,  // Green
    0x80,  // Red
    0x00   // Reserved
  }
};

//
// Default color of progress completion.  White.
//
const EFI_GRAPHICS_OUTPUT_BLT_PIXEL_UNION  mProgressBarDefaultColor = {
  {
    0xFF,  // Blue
    0xFF,  // Green
    0xFF,  // Red
    0x00   // Reserved
  }
};

//
// Set to TRUE if a valid Graphics Output Protocol is found and the progress
// bar fits under the boot logo using the current graphics mode.
//
BOOLEAN mGraphicsGood = FALSE;

/**
  Internal function used to find the bounds of the white logo (on black or
  red background).

  These bounds are then computed to find the block size, 0%, 100%, etc.

**/
VOID
FindDim (
   VOID
  )
{
  EFI_STATUS                           Status;
  INTN                                 LogoX;
  INTN                                 LogoStartX;
  INTN                                 LogoEndX;
  INTN                                 LogoY;
  INTN                                 LogoStartY;
  INTN                                 LogoEndY;
  UINTN                                OffsetX;     // Logo screen coordinate
  UINTN                                OffsetY;     // Logo screen coordinate
  UINTN                                Width;       // Width of logo in pixels
  UINTN                                Height;      // Height of logo in pixels
  EFI_GRAPHICS_OUTPUT_BLT_PIXEL        *Logo;
  EDKII_BOOT_LOGO2_PROTOCOL            *BootLogo;
  EFI_GRAPHICS_OUTPUT_BLT_PIXEL_UNION  *Pixel;

  Logo     = NULL;
  BootLogo = NULL;

  //
  // Return if a Graphics Output Protocol ha snot been found.
  //
  if (mGop == NULL) {
    DEBUG ((DEBUG_ERROR, "No GOP found.  No progress bar support. \n"));
    return;
  }

  //
  // Get boot logo protocol so we know where on the screen to grab
  //
  Status = gBS->LocateProtocol (
                  &gEdkiiBootLogo2ProtocolGuid,
                  NULL,
                  (VOID **)&BootLogo
                  );
  if ((BootLogo == NULL) || (EFI_ERROR (Status))) {
    DEBUG ((DEBUG_ERROR, "Failed to locate gEdkiiBootLogo2ProtocolGuid.  No Progress bar support. \n", Status));
    return;
  }

  //
  // Get logo location and size
  //
  Status = BootLogo->GetBootLogo (
                       BootLogo,
                       &Logo,
                       &OffsetX,
                       &OffsetY,
                       &Width,
                       &Height
                       );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "Failed to Get Boot Logo Status = %r.  No Progress bar support. \n", Status));
    return;
  }

  //
  // Within logo buffer find where the actual logo starts/ends
  //
  LogoEndX = 0;
  LogoEndY = 0;

  //
  // Find left side of logo in logo coordinates
  //
  for (LogoX = 0, LogoStartX = Width; LogoX < LogoStartX; LogoX++) {
    Pixel = (EFI_GRAPHICS_OUTPUT_BLT_PIXEL_UNION *)(Logo + LogoX);
    for (LogoY = 0; LogoY < (INTN)Height; LogoY++) {
      if ((Pixel->Raw & mLogoDetectionColorMask.Raw) != 0x0) {
        LogoStartX = LogoX;
        //
        // For loop searches from right side back to this column.
        //
        LogoEndX = LogoX;
        DEBUG ((DEBUG_INFO, "StartX found at (%d, %d) Color is: 0x%X \n", LogoX, LogoY, Pixel->Raw));
        break;
      }
      Pixel = Pixel + Width;
    }
  }

  //
  // Find right side of logo
  //
  for (LogoX = Width - 1; LogoX >= LogoEndX; LogoX--) {
    Pixel = (EFI_GRAPHICS_OUTPUT_BLT_PIXEL_UNION *)(Logo + LogoX);
    for (LogoY = 0; LogoY < (INTN)Height; LogoY++) {
      if ((Pixel->Raw & mLogoDetectionColorMask.Raw) != 0x0) {
        LogoEndX = LogoX;
        DEBUG ((DEBUG_INFO, "EndX found at (%d, %d) Color is: 0x%X \n", LogoX, LogoY, Pixel->Raw));
        break;
      }
      Pixel = Pixel + Width;
    }
  }

  //
  // Compute mBlockWidth
  //
  mBlockWidth = ((LogoEndX - LogoStartX) + 99) / 100;

  //
  // Adjust mStartX based on block width so it is centered under logo
  //
  mStartX = LogoStartX + OffsetX - (((mBlockWidth * 100) - (LogoEndX - LogoStartX)) / 2);
  DEBUG ((DEBUG_INFO, "mBlockWidth set to 0x%X\n", mBlockWidth));
  DEBUG ((DEBUG_INFO, "mStartX set to 0x%X\n", mStartX));

  //
  // Find the top of the logo
  //
  for (LogoY = 0, LogoStartY = Height; LogoY < LogoStartY; LogoY++) {
    Pixel = (EFI_GRAPHICS_OUTPUT_BLT_PIXEL_UNION *)(Logo + (Width * LogoY));
    for (LogoX = 0; LogoX < (INTN)Width; LogoX++) {
      //not black or red
      if ((Pixel->Raw & mLogoDetectionColorMask.Raw) != 0x0) {
        LogoStartY = LogoY;
        LogoEndY = LogoY; //for next loop will search from bottom side back to this row.
        DEBUG ((DEBUG_INFO, "StartY found at (%d, %d) Color is: 0x%X \n", LogoX, LogoY, Pixel->Raw));
        break;
      }
      Pixel++;
    }
  }

  //
  // Find the bottom of the logo
  //
  for (LogoY = Height - 1; LogoY >= LogoEndY; LogoY--) {
    Pixel = (EFI_GRAPHICS_OUTPUT_BLT_PIXEL_UNION *)(Logo + (Width * LogoY));
    for (LogoX = 0; LogoX < (INTN)Width; LogoX++) {
      if ((Pixel->Raw & mLogoDetectionColorMask.Raw) != 0x0) {
        LogoEndY = LogoY;
        DEBUG ((DEBUG_INFO, "EndY found at (%d, %d) Color is: 0x%X \n", LogoX, LogoY, Pixel->Raw));
        break;
      }
      Pixel++;
    }
  }

  //
  // Compute bottom padding (distance between logo bottom and progress bar)
  //
  mStartY = (((LogoEndY - LogoStartY) * LOGO_BOTTOM_PADDING) / 100) + LogoEndY + OffsetY;

  //
  // Compute progress bar height
  //
  mBlockHeight = (((LogoEndY - LogoStartY) * PROGRESS_BLOCK_HEIGHT) / 100);

  DEBUG ((DEBUG_INFO, "mBlockHeight set to 0x%X\n", mBlockHeight));

  //
  // Create progress bar background (one time init).
  //
  mProgressBarBackground = AllocatePool (mBlockWidth * 100 * mBlockHeight * sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL));
  if (mProgressBarBackground == NULL) {
    DEBUG ((DEBUG_ERROR, "Failed to allocate progress bar background\n"));
    return;
  }

  //
  // Fill the progress bar with the background color
  //
  SetMem32 (
    mProgressBarBackground,
    (mBlockWidth * 100 * mBlockHeight * sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL)),
    mProgressBarBackgroundColor.Raw
    );

  //
  // Allocate mBlockBitmap
  //
  mBlockBitmap = AllocatePool (mBlockWidth * mBlockHeight * sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL));
  if (mBlockBitmap == NULL) {
    FreePool (mProgressBarBackground);
    DEBUG ((DEBUG_ERROR, "Failed to allocate block\n"));
    return;
  }

  //
  // Check screen width and height and make sure it fits.
  //
  if ((mBlockHeight > Height) || (mBlockWidth > Width) || (mBlockHeight < 1) || (mBlockWidth < 1)) {
    DEBUG ((DEBUG_ERROR, "DisplayUpdateProgressLib - Progress - Failed to get valid width and height.\n"));
    DEBUG ((DEBUG_ERROR, "DisplayUpdateProgressLib - Progress - mBlockHeight: 0x%X  mBlockWidth: 0x%X.\n", mBlockHeight, mBlockWidth));
    FreePool (mProgressBarBackground);
    FreePool (mBlockBitmap);
    return;
  }

  mGraphicsGood = TRUE;
}

/**
  Function indicates the current completion progress of a firmware update.
  Platform may override with its own specific function.

  @param[in] Completion  A value between 0 and 100 indicating the current
                         completion progress of a firmware update.  This
                         value must the the same or higher than previous
                         calls to this service.  The first call of 0 or a
                         value of 0 after reaching a value of 100 resets
                         the progress indicator to 0.
  @param[in] Color       Color of the progress indicator.  Only used when
                         Completion is 0 to set the color of the progress
                         indicator.  If Color is NULL, then the default color
                         is used.

  @retval EFI_SUCCESS            Progress displayed successfully.
  @retval EFI_INVALID_PARAMETER  Completion is not in range 0..100.
  @retval EFI_INVALID_PARAMETER  Completion is less than Completion value from
                                 a previous call to this service.
  @retval EFI_NOT_READY          The device used to indicate progress is not
                                 available.
**/
EFI_STATUS
EFIAPI
DisplayUpdateProgress (
  IN UINTN                                Completion,
  IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL_UNION  *Color       OPTIONAL
  )
{
  EFI_STATUS  Status;
  UINTN       PreX;
  UINTN       Index;

  //
  // Check range
  //
  if (Completion > 100) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Check to see if this Completion percentage has already been displayed
  //
  if (Completion == mPreviousProgress) {
    return EFI_SUCCESS;
  }

  //
  // Find Graphics Output Protocol if not already set.  1 time.
  //
  if (mGop == NULL) {
    Status = gBS->HandleProtocol (
                    gST->ConsoleOutHandle,
                    &gEfiGraphicsOutputProtocolGuid,
                    (VOID**)&mGop
                    );
    if (EFI_ERROR (Status)) {
      Status = gBS->LocateProtocol (&gEfiGraphicsOutputProtocolGuid, NULL, (VOID **)&mGop);
      if (EFI_ERROR (Status)) {
        mGop = NULL;
        DEBUG ((DEBUG_ERROR, "Show Progress Function could not locate GOP.  Status = %r\n", Status));
        return EFI_NOT_READY;
      }
    }

    //
    // Run once
    //
    FindDim ();
  }

  //
  // Make sure a valid start, end, and size info are available (find the Logo)
  //
  if (!mGraphicsGood) {
    DEBUG ((DEBUG_INFO, "Graphics Not Good.  Not doing any onscreen visual display\n"));
    return EFI_NOT_READY;
  }

  //
  // Do special init on first call of each progress session
  //
  if (mPreviousProgress == 100) {
    //
    // Draw progress bar background
    //
    mGop->Blt (
            mGop,
            mProgressBarBackground,
            EfiBltBufferToVideo,
            0,
            0,
            mStartX,
            mStartY,
            (mBlockWidth * 100),
            mBlockHeight,
            0
            );

    DEBUG ((DEBUG_VERBOSE, "Color is 0x%X\n",
      (Color == NULL) ? mProgressBarDefaultColor.Raw : Color->Raw
      ));

    //
    // Update block bitmap with correct color
    //
    SetMem32 (
      mBlockBitmap,
      (mBlockWidth * mBlockHeight * sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL)),
      (Color == NULL) ? mProgressBarDefaultColor.Raw : Color->Raw
      );

    //
    // Clear previous
    //
    mPreviousProgress = 0;
  }

  //
  // Can not update progress bar if Completion is less than previous
  //
  if (Completion < mPreviousProgress) {
    DEBUG ((DEBUG_WARN, "WARNING: Completion (%d) should not be lesss than Previous (%d)!!!\n", Completion, mPreviousProgress));
    return EFI_INVALID_PARAMETER;
  }

  PreX = ((mPreviousProgress * mBlockWidth) + mStartX);
  for (Index = 0; Index < (Completion - mPreviousProgress); Index++) {
    //
    // Show progress by coloring new area
    //
    mGop->Blt (
            mGop,
            mBlockBitmap,
            EfiBltBufferToVideo,
            0,
            0,
            PreX,
            mStartY,
            mBlockWidth,
            mBlockHeight,
            0
            );
    PreX += mBlockWidth;
  }

  mPreviousProgress = Completion;

  return EFI_SUCCESS;
}