1 /**
2  * @file lv_uefi_display.c
3  *
4  */
5 
6 /*********************
7  *      INCLUDES
8  *********************/
9 
10 #include "../../lvgl.h"
11 
12 #if LV_USE_UEFI
13 
14 #include "lv_uefi_display.h"
15 #include "lv_uefi_private.h"
16 
17 #if LV_COLOR_DEPTH != 32
18     #error [lv_uefi] Unsupported LV_COLOR_DEPTH.
19 #endif
20 
21 /*********************
22  *      DEFINES
23  *********************/
24 
25 /**********************
26  *      TYPEDEFS
27  **********************/
28 typedef struct _lv_uefi_display_context_t {
29     EFI_HANDLE handle;
30     EFI_GRAPHICS_OUTPUT_PROTOCOL * gop_protocol;
31     void * buffer;
32     size_t buffer_size;
33 } lv_uefi_display_context_t;
34 
35 /**********************
36  *  STATIC PROTOTYPES
37  **********************/
38 
39 static void _display_event_cb(lv_event_t * e);
40 static void _display_flush_cb(lv_display_t * display, const lv_area_t * area, uint8_t * px_map);
41 
42 static void _display_ctx_free(lv_uefi_display_context_t * display_ctx);
43 static bool _display_interface_is_valid(const EFI_GRAPHICS_OUTPUT_PROTOCOL * interface);
44 
45 /**********************
46  *  GOLBAL VARIABLES
47  **********************/
48 
49 /**********************
50  *  STATIC VARIABLES
51  **********************/
52 
53 static EFI_GUID _uefi_guid_graphics_output = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;
54 static EFI_GUID _uefi_guid_edid_active = EFI_EDID_ACTIVE_PROTOCOL_GUID;
55 
56 /**********************
57  *      MACROS
58  **********************/
59 
60 /**********************
61  *   GLOBAL FUNCTIONS
62  **********************/
63 
64 /**
65  * @brief Create a LVGL display object.
66  * @param handle The handle on which an instance of the EFI_GRAPHICS_OUTPUT_PROTOCOL protocol is installed.
67  * @return The created LVGL display object.
68  */
lv_uefi_display_create(void * handle)69 lv_display_t * lv_uefi_display_create(void * handle)
70 {
71     lv_display_t * display = NULL;
72     lv_uefi_display_context_t * display_ctx;
73 
74     if(!lv_uefi_protocol_test(handle, &_uefi_guid_graphics_output)) return NULL;
75 
76     display_ctx = lv_calloc(1, sizeof(lv_uefi_display_context_t));
77     LV_ASSERT_MALLOC(display_ctx);
78 
79     display_ctx->handle = handle;
80     display_ctx->gop_protocol = (EFI_GRAPHICS_OUTPUT_PROTOCOL *)lv_uefi_protocol_open(handle, &_uefi_guid_graphics_output);
81     if(!_display_interface_is_valid(display_ctx->gop_protocol)) {
82         LV_LOG_WARN("[lv_uefi] The GOP interface is not valid.");
83         goto error;
84     }
85 
86     // 4 bytes per pixel
87     display_ctx->buffer_size = 4 * display_ctx->gop_protocol->Mode->Info->HorizontalResolution *
88                                display_ctx->gop_protocol->Mode->Info->VerticalResolution;
89     display_ctx->buffer = lv_malloc(display_ctx->buffer_size);
90     LV_ASSERT_MALLOC(display_ctx->buffer);
91 
92     display = lv_display_create(display_ctx->gop_protocol->Mode->Info->HorizontalResolution,
93                                 display_ctx->gop_protocol->Mode->Info->VerticalResolution);
94     lv_display_add_event_cb(display, _display_event_cb, LV_EVENT_DELETE, display);
95     lv_display_set_flush_cb(display, _display_flush_cb);
96     lv_display_set_buffers(display, display_ctx->buffer, NULL, (uint32_t)display_ctx->buffer_size,
97                            LV_DISPLAY_RENDER_MODE_DIRECT);
98     lv_display_set_user_data(display, display_ctx);
99 
100     goto finish;
101 
102 error:
103     if(display != NULL) {
104         lv_display_set_user_data(display, NULL);
105         lv_display_delete(display);
106         display = NULL;
107     }
108 
109     if(display_ctx != NULL) _display_ctx_free(display_ctx);
110 
111 finish:
112     return display;
113 }
114 
115 /**
116  * @brief Try to find the active display handle.
117  * @return The handle or NULL if not found.
118  * @remark The active display need interfaces for EFI_GRAPHICS_OUTPUT_PROTOCOL and EFI_EDID_ACTIVE_PROTOCOL
119 */
lv_uefi_display_get_active(void)120 void * lv_uefi_display_get_active(void)
121 {
122     EFI_STATUS status;
123     EFI_HANDLE active_handle = NULL;
124     EFI_HANDLE * handles = NULL;
125     UINTN no_handles;
126     UINTN index;
127 
128     status = gLvEfiBS->LocateHandleBuffer(
129                  ByProtocol,
130                  &_uefi_guid_graphics_output,
131                  NULL,
132                  &no_handles,
133                  &handles);
134     if(status != EFI_SUCCESS) goto error;
135 
136     for(index = 0; index < no_handles; index++) {
137         if(!lv_uefi_protocol_test(handles[index], &_uefi_guid_edid_active)) continue;
138         if(!lv_uefi_protocol_test(handles[index], &_uefi_guid_graphics_output)) continue;
139         active_handle = handles[index];
140         break;
141     }
142 
143     goto finish;
144 
145 error:
146 
147 finish:
148     if(handles != NULL) gLvEfiBS->FreePool(handles);
149 
150     return active_handle;
151 }
152 
153 /**
154  * @brief Try to find any display handle.
155  * @return The handle or NULL if not found.
156 */
lv_uefi_display_get_any(void)157 void * lv_uefi_display_get_any(void)
158 {
159     EFI_STATUS status;
160     EFI_HANDLE active_handle = NULL;
161     EFI_HANDLE * handles = NULL;
162     UINTN no_handles;
163     UINTN index;
164 
165     status = gLvEfiBS->LocateHandleBuffer(
166                  ByProtocol,
167                  &_uefi_guid_graphics_output,
168                  NULL,
169                  &no_handles,
170                  &handles);
171     if(status != EFI_SUCCESS) goto error;
172 
173     for(index = 0; index < no_handles; index++) {
174         if(!lv_uefi_protocol_test(handles[index], &_uefi_guid_graphics_output)) continue;
175         active_handle = handles[index];
176         break;
177     }
178 
179     goto finish;
180 
181 error:
182 
183 finish:
184     if(handles != NULL) gLvEfiBS->FreePool(handles);
185 
186     return active_handle;
187 }
188 
189 /**********************
190  *   STATIC FUNCTIONS
191  **********************/
192 
_display_event_cb(lv_event_t * e)193 static void _display_event_cb(lv_event_t * e)
194 {
195     lv_display_t * display;
196     lv_uefi_display_context_t * display_ctx;
197 
198     if(lv_event_get_code(e) != LV_EVENT_DELETE) return;
199 
200     display = (lv_display_t *)lv_event_get_user_data(e);
201     if(display == NULL) return;
202 
203     display_ctx = (lv_uefi_display_context_t *)lv_display_get_user_data(display);
204     lv_display_set_user_data(display, NULL);
205 
206     if(display_ctx != NULL) _display_ctx_free(display_ctx);
207 }
208 
_display_flush_cb(lv_display_t * display,const lv_area_t * area,uint8_t * px_map)209 static void _display_flush_cb(lv_display_t * display, const lv_area_t * area, uint8_t * px_map)
210 {
211     EFI_STATUS status;
212     int32_t w;
213     int32_t h;
214 
215     lv_uefi_display_context_t * display_ctx = (lv_uefi_display_context_t *)lv_display_get_user_data(display);
216     LV_ASSERT_NULL(display_ctx);
217 
218     w = (int32_t)area->x2 - (int32_t)area->x1 + 1;
219     h = (int32_t)area->y2 - (int32_t)area->y1 + 1;
220 
221     if(w < 0 || h < 0) {
222         LV_LOG_ERROR("[lv_uefi] Invalid lv_display_flush_cb call (invalid rect).");
223         goto error;
224     }
225 
226     if((uint32_t)(area->x1 + w) > display_ctx->gop_protocol->Mode->Info->HorizontalResolution) {
227         LV_LOG_ERROR("[lv_uefi] Invalid lv_display_flush_cb call (invalid width).");
228         goto error;
229     }
230 
231     if((uint32_t)(area->y1 + h) > display_ctx->gop_protocol->Mode->Info->HorizontalResolution) {
232         LV_LOG_ERROR("[lv_uefi] Invalid lv_display_flush_cb call (invalid height).");
233         goto error;
234     }
235 
236     status = display_ctx->gop_protocol->Blt(
237                  display_ctx->gop_protocol,
238                  (EFI_GRAPHICS_OUTPUT_BLT_PIXEL *)px_map,
239                  EfiBltBufferToVideo,
240                  area->x1,
241                  area->y1,
242                  area->x1,
243                  area->y1,
244                  w,
245                  h,
246                  display_ctx->gop_protocol->Mode->Info->HorizontalResolution * sizeof(EFI_GRAPHICS_OUTPUT_BLT_PIXEL));
247     if(status != EFI_SUCCESS) {
248         LV_LOG_ERROR("[lv_uefi] Blt failed with error code: %llx.", status);
249         goto error;
250     }
251 
252     goto finish;
253 
254 error:
255 
256 finish:
257     lv_display_flush_ready(display);
258 }
259 
_display_ctx_free(lv_uefi_display_context_t * display_ctx)260 static void _display_ctx_free(lv_uefi_display_context_t * display_ctx)
261 {
262     if(display_ctx == NULL) {
263         return;
264     }
265 
266     if(display_ctx->gop_protocol != NULL) lv_uefi_protocol_close(display_ctx->handle, &_uefi_guid_graphics_output);
267     if(display_ctx->buffer != NULL) lv_free(display_ctx->buffer);
268 
269     lv_free(display_ctx);
270 }
271 
_display_interface_is_valid(const EFI_GRAPHICS_OUTPUT_PROTOCOL * interface)272 static bool _display_interface_is_valid(const EFI_GRAPHICS_OUTPUT_PROTOCOL * interface)
273 {
274     if(interface == NULL) return FALSE;
275     if(interface->Mode == NULL) return FALSE;
276     if(interface->Mode->Info == NULL) return FALSE;
277     if(interface->Mode->Info->HorizontalResolution == 0) return FALSE;
278     if(interface->Mode->Info->HorizontalResolution >= 32767) return FALSE;
279     if(interface->Mode->Info->VerticalResolution == 0) return FALSE;
280     if(interface->Mode->Info->VerticalResolution >= 32767) return FALSE;
281 
282     return TRUE;
283 }
284 #endif