1// Copyright 2016 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <framebuffer.h>
6#include <xefi.h>
7
8#include <stdio.h>
9#include <stdlib.h>
10#include <string.h>
11
12static efi_graphics_output_protocol* fb_get_gop() {
13    static efi_graphics_output_protocol* gop = NULL;
14    if (!gop) {
15        gBS->LocateProtocol(&GraphicsOutputProtocol, NULL, (void**)&gop);
16    }
17    return gop;
18}
19
20uint32_t get_gfx_mode() {
21    efi_graphics_output_protocol* gop = fb_get_gop();
22    return gop->Mode->Mode;
23}
24
25uint32_t get_gfx_max_mode() {
26    efi_graphics_output_protocol* gop = fb_get_gop();
27    return gop->Mode->MaxMode;
28}
29
30uint32_t get_gfx_hres() {
31    efi_graphics_output_protocol* gop = fb_get_gop();
32    efi_graphics_output_mode_information* mode_info;
33    size_t info_size = 0;
34    efi_status status = gop->QueryMode(gop, gop->Mode->Mode, &info_size, &mode_info);
35    if (EFI_ERROR(status)) {
36        return 0;
37    }
38    return mode_info->HorizontalResolution;
39}
40
41uint32_t get_gfx_vres() {
42    efi_graphics_output_protocol* gop = fb_get_gop();
43    efi_graphics_output_mode_information* mode_info;
44    size_t info_size = 0;
45    efi_status status = gop->QueryMode(gop, gop->Mode->Mode, &info_size, &mode_info);
46    if (EFI_ERROR(status)) {
47        return 0;
48    }
49    return mode_info->VerticalResolution;
50}
51
52void set_gfx_mode(uint32_t mode) {
53    efi_graphics_output_protocol* gop = fb_get_gop();
54    if (!gop)
55        return;
56    if (mode >= gop->Mode->MaxMode) {
57        printf("invalid framebuffer mode: %u\n", mode);
58        return;
59    }
60    efi_status s = gop->SetMode(gop, mode);
61    if (EFI_ERROR(s)) {
62        printf("could not set mode: %s\n", xefi_strerror(s));
63    }
64    gBS->Stall(1000);
65    gSys->ConOut->SetCursorPosition(gSys->ConOut, 0, 0);
66}
67
68void set_gfx_mode_from_cmdline(const char* fbres) {
69    if (!fbres)
70        return;
71    efi_graphics_output_protocol* gop = fb_get_gop();
72    if (!gop)
73        return;
74
75    uint32_t hres = 0;
76    hres = atol(fbres);
77
78    char* x = strchr(fbres, 'x');
79    if (!x)
80        return;
81    x++;
82
83    uint32_t vres = 0;
84    vres = atol(x);
85    if (!hres || !vres)
86        return;
87
88    uint32_t max_mode = gop->Mode->MaxMode;
89
90    for (int i = 0; i < max_mode; i++) {
91        efi_graphics_output_mode_information* mode_info;
92        size_t info_size = 0;
93        efi_status status = gop->QueryMode(gop, i, &info_size, &mode_info);
94        if (EFI_ERROR(status)) {
95            printf("Could not retrieve mode %d: %s\n", i, xefi_strerror(status));
96            continue;
97        }
98
99        if (mode_info->HorizontalResolution == hres &&
100            mode_info->VerticalResolution == vres) {
101            set_gfx_mode(i);
102            return;
103        }
104    }
105    printf("Could not find framebuffer mode %ux%u; using default mode = %ux%u\n",
106           hres, vres, gop->Mode->Info->HorizontalResolution, gop->Mode->Info->VerticalResolution);
107    gBS->Stall(5000000);
108}
109
110void print_fb_modes() {
111    efi_graphics_output_protocol* gop = fb_get_gop();
112    uint32_t max_mode = gop->Mode->MaxMode;
113    uint32_t cur_mode = gop->Mode->Mode;
114    for (int i = 0; i < max_mode; i++) {
115        efi_graphics_output_mode_information* mode_info;
116        size_t info_size = 0;
117        efi_status status = gop->QueryMode(gop, i, &info_size, &mode_info);
118        if (EFI_ERROR(status))
119            continue;
120        printf(" (%u) %u x %u%s\n", i, mode_info->HorizontalResolution,
121               mode_info->VerticalResolution, i == cur_mode ? " (current)" : "");
122    }
123}
124
125#include "logo.h"
126
127static efi_graphics_output_blt_pixel font_white = {
128    .Red = 0xFF,
129    .Green = 0xFF,
130    .Blue = 0xFF,
131};
132
133static efi_graphics_output_blt_pixel font_black = {
134    .Red = 0x0,
135    .Green = 0x0,
136    .Blue = 0x0,
137};
138
139static efi_graphics_output_blt_pixel font_fuchsia = {
140    .Red = 0xFF,
141    .Green = 0x0,
142    .Blue = 0x80,
143};
144
145void draw_logo() {
146    efi_graphics_output_protocol* gop = fb_get_gop();
147    if (!gop)
148        return;
149
150    const uint32_t h_res = gop->Mode->Info->HorizontalResolution;
151    const uint32_t v_res = gop->Mode->Info->VerticalResolution;
152
153    // Blank the screen, removing vendor UEFI logos
154    gop->Blt(gop, &font_black, EfiBltVideoFill, 0, 0, 0, 0, h_res, v_res, 0);
155
156    efi_graphics_output_blt_pixel* tmp;
157    unsigned sz = sizeof(efi_graphics_output_blt_pixel) * logo_width * logo_height;
158    if (EFI_ERROR(gBS->AllocatePool(EfiLoaderData, sz, (void*)&tmp))) {
159        // Draw the Fuchsia stripe on the top of the screen
160        gop->Blt(gop, &font_fuchsia, EfiBltVideoFill, 0, 0, 0, 0, h_res, v_res / 100, 0);
161        return;
162    }
163
164    // Un-RLE the logo
165    unsigned char* iptr = logo_rle;
166    efi_graphics_output_blt_pixel* optr = tmp;
167    unsigned entries = sizeof(logo_rle) / 2;
168    while (entries-- > 0) {
169        unsigned count = *iptr++;
170        unsigned alpha = *iptr++;
171        efi_graphics_output_blt_pixel px = {
172            .Red = (alpha * 0xFF) / 255,
173            .Green = 0,
174            .Blue = (alpha * 0x80) / 255,
175        };
176        while (count-- > 0) {
177            *optr++ = px;
178        }
179    }
180
181    gop->Blt(gop, tmp, EfiBltBufferToVideo, 0, 0,
182             h_res - logo_width - (h_res / 75), v_res - logo_height - (v_res / 75),
183             logo_width, logo_height, 0);
184}
185
186#include <zircon/font/font-9x16.h>
187#include <zircon/font/font-18x32.h>
188
189static void putchar(efi_graphics_output_protocol* gop, fb_font* font, unsigned ch, unsigned x, unsigned y, unsigned scale_x, unsigned scale_y, efi_graphics_output_blt_pixel* fg, efi_graphics_output_blt_pixel* bg) {
190    const uint16_t* cdata = font->data + ch * font->height;
191    unsigned fw = font->width;
192    for (unsigned i = 0; i <= font->height; i++) {
193        uint16_t xdata = *cdata++;
194        for (unsigned j = 0; j < fw; j++) {
195            gop->Blt(gop, (xdata & 1) ? fg : bg, EfiBltVideoFill, 0, 0, x + scale_x * j, y + scale_y * i, scale_x, scale_y, 0);
196            xdata >>= 1;
197        }
198    }
199}
200
201void draw_text(const char* text, size_t length, fb_font* font, int x, int y) {
202    efi_graphics_output_protocol* gop = fb_get_gop();
203    efi_graphics_output_blt_pixel* fg_color = &font_white;
204    if (!gop)
205        return;
206
207    if (font->color != NULL) {
208        fg_color = font->color;
209    }
210
211    size_t offset = 0;
212    size_t scale = 1;
213    for (size_t i = 0; i < length; ++i) {
214        unsigned char c = text[i];
215        if (c > 127)
216            continue;
217        putchar(gop, font, c, x + offset, y, scale, scale, fg_color, &font_black);
218        offset += font->width * scale;
219    }
220}
221
222void draw_nodename(const char* nodename) {
223    efi_graphics_output_protocol* gop = fb_get_gop();
224    if (!gop)
225        return;
226
227    fb_font font = {
228        .data = FONT18X32,
229        .width = FONT18X32_WIDTH,
230        .height = FONT18X32_HEIGHT,
231        .color = &font_white,
232    };
233
234    const uint32_t h_res = gop->Mode->Info->HorizontalResolution;
235    const uint32_t v_res = gop->Mode->Info->VerticalResolution;
236    size_t length = strlen(nodename);
237    draw_text(nodename, length, &font, h_res - (length + 1) * font.width, v_res / 100 + font.height);
238}
239
240void draw_version(const char* version) {
241    efi_graphics_output_protocol* gop = fb_get_gop();
242    if (!gop)
243        return;
244
245    const char* prefix = "GigaBoot 20X6 - Version";
246    size_t prefix_len = strlen(prefix);
247    size_t version_len = strlen(version);
248
249    fb_font font = {
250        .data = FONT9X16,
251        .width = FONT9X16_WIDTH,
252        .height = FONT9X16_HEIGHT,
253        .color = &font_fuchsia,
254    };
255
256    draw_text(prefix, prefix_len, &font, 0, 0);
257    draw_text(version, version_len, &font, (prefix_len + 1) * font.width, 0);
258}
259