1 /** @file
2  *
3  *  Copyright (c) 2017, Andrey Warkentin <andrey.warkentin@gmail.com>
4  *  Copyright (c) 2016, Linaro, Ltd. All rights reserved.
5  *
6  *  SPDX-License-Identifier: BSD-2-Clause-Patent
7  *
8  **/
9 #include <PiDxe.h>
10 
11 #include <Library/BaseLib.h>
12 #include <Library/DebugLib.h>
13 #include <Library/DxeServicesLib.h>
14 #include <Library/MemoryAllocationLib.h>
15 #include <Library/UefiBootServicesTableLib.h>
16 #include <Library/UefiLib.h>
17 #include <libfdt.h>
18 #include <Protocol/RpiFirmware.h>
19 #include <Guid/Fdt.h>
20 #include <ConfigVars.h>
21 
22 STATIC VOID                             *mFdtImage;
23 
24 STATIC RASPBERRY_PI_FIRMWARE_PROTOCOL   *mFwProtocol;
25 
26 STATIC
27 EFI_STATUS
28 FixEthernetAliases (
29   VOID
30 )
31 {
32   INTN          Aliases;
33   CONST CHAR8   *Ethernet;
34   CONST CHAR8   *Ethernet0;
35   CONST CHAR8   *Alias;
36   UINTN         CopySize;
37   CHAR8         *Copy;
38   INTN          Retval;
39   EFI_STATUS    Status;
40 
41   //
42   // Look up the 'ethernet[0]' aliases
43   //
44   Aliases = fdt_path_offset (mFdtImage, "/aliases");
45   if (Aliases < 0) {
46     DEBUG ((DEBUG_ERROR, "%a: failed to locate '/aliases'\n", __FUNCTION__));
47     return EFI_NOT_FOUND;
48   }
49   Ethernet = fdt_getprop (mFdtImage, Aliases, "ethernet", NULL);
50   Ethernet0 = fdt_getprop (mFdtImage, Aliases, "ethernet0", NULL);
51   Alias = Ethernet ? Ethernet : Ethernet0;
52   if (!Alias) {
53     DEBUG ((DEBUG_ERROR, "%a: failed to locate 'ethernet[0]' alias\n", __FUNCTION__));
54     return EFI_NOT_FOUND;
55   }
56 
57   //
58   // Create copy for fdt_setprop
59   //
60   CopySize = AsciiStrSize (Alias);
61   Copy = AllocateCopyPool (CopySize, Alias);
62   if (!Copy) {
63     DEBUG ((DEBUG_ERROR, "%a: failed to copy '%a'\n", __FUNCTION__, Alias));
64     return EFI_OUT_OF_RESOURCES;
65   }
66 
67   //
68   // Create missing aliases
69   //
70   Status = EFI_SUCCESS;
71   if (!Ethernet) {
72     Retval = fdt_setprop (mFdtImage, Aliases, "ethernet", Copy, CopySize);
73     if (Retval != 0) {
74       Status = EFI_NOT_FOUND;
75       DEBUG ((DEBUG_ERROR, "%a: failed to create 'ethernet' alias (%d)\n",
76         __FUNCTION__, Retval));
77     }
78     DEBUG ((DEBUG_INFO, "%a: created 'ethernet' alias '%a'\n", __FUNCTION__, Copy));
79   }
80   if (!Ethernet0) {
81     Retval = fdt_setprop (mFdtImage, Aliases, "ethernet0", Copy, CopySize);
82     if (Retval != 0) {
83       Status = EFI_NOT_FOUND;
84       DEBUG ((DEBUG_ERROR, "%a: failed to create 'ethernet0' alias (%d)\n",
85         __FUNCTION__, Retval));
86     }
87     DEBUG ((DEBUG_INFO, "%a: created 'ethernet0' alias '%a'\n", __FUNCTION__, Copy));
88   }
89 
90   FreePool (Copy);
91   return Status;
92 }
93 
94 STATIC
95 EFI_STATUS
96 UpdateMacAddress (
97   VOID
98   )
99 {
100   INTN          Node;
101   INTN          Retval;
102   EFI_STATUS    Status;
103   UINT8         MacAddress[6];
104 
105   //
106   // Locate the node that the 'ethernet' alias refers to
107   //
108   Node = fdt_path_offset (mFdtImage, "ethernet");
109   if (Node < 0) {
110     DEBUG ((DEBUG_ERROR, "%a: failed to locate 'ethernet' alias\n", __FUNCTION__));
111     return EFI_NOT_FOUND;
112   }
113 
114   //
115   // Get the MAC address from the firmware
116   //
117   Status = mFwProtocol->GetMacAddress (MacAddress);
118   if (EFI_ERROR (Status)) {
119     DEBUG ((DEBUG_ERROR, "%a: failed to retrieve MAC address\n", __FUNCTION__));
120     return Status;
121   }
122 
123   Retval = fdt_setprop (mFdtImage, Node, "mac-address", MacAddress,
124     sizeof MacAddress);
125   if (Retval != 0) {
126     DEBUG ((DEBUG_ERROR, "%a: failed to create 'mac-address' property (%d)\n",
127       __FUNCTION__, Retval));
128     return EFI_NOT_FOUND;
129   }
130 
131   DEBUG ((DEBUG_INFO, "%a: setting MAC address to %02x:%02x:%02x:%02x:%02x:%02x\n",
132     __FUNCTION__, MacAddress[0], MacAddress[1], MacAddress[2], MacAddress[3],
133     MacAddress[4], MacAddress[5]));
134   return EFI_SUCCESS;
135 }
136 
137 //
138 // Add "bcm2835-usb" to the USB compatible property list, if not present.
139 // Required because some Linux kernels can't handle USB devices otherwise.
140 //
141 STATIC
142 EFI_STATUS
143 AddUsbCompatibleProperty (
144   VOID
145   )
146 {
147   CONST CHAR8   Prop[]    = "brcm,bcm2708-usb";
148   CONST CHAR8   NewProp[] = "brcm,bcm2835-usb";
149   CONST CHAR8   *List;
150   CHAR8         *NewList;
151   INT32         ListSize;
152   INTN          Node;
153   INTN          Retval;
154 
155   // Locate the node that the 'usb' alias refers to
156   Node = fdt_path_offset (mFdtImage, "usb");
157   if (Node < 0) {
158     DEBUG ((DEBUG_ERROR, "%a: failed to locate 'usb' alias\n", __FUNCTION__));
159     return EFI_NOT_FOUND;
160   }
161 
162   // Get the property list. This is a list of NUL terminated strings.
163   List = fdt_getprop (mFdtImage, Node, "compatible", &ListSize);
164   if (List == NULL) {
165     DEBUG ((DEBUG_ERROR, "%a: failed to locate properties\n", __FUNCTION__));
166     return EFI_NOT_FOUND;
167   }
168 
169   // Check if the compatible value we plan to add is already present
170   if (fdt_stringlist_contains (List, ListSize, NewProp)) {
171     DEBUG ((DEBUG_INFO, "%a: property '%a' is already set.\n",
172       __FUNCTION__, NewProp));
173     return EFI_SUCCESS;
174   }
175 
176   // Make sure the compatible device is what we expect
177   if (!fdt_stringlist_contains (List, ListSize, Prop)) {
178     DEBUG ((DEBUG_ERROR, "%a: property '%a' is missing!\n",
179       __FUNCTION__, Prop));
180     return EFI_NOT_FOUND;
181   }
182 
183   // Add the new NUL terminated entry to our list
184   DEBUG ((DEBUG_INFO, "%a: adding '%a' to the properties\n",
185     __FUNCTION__, NewProp));
186 
187   NewList = AllocatePool (ListSize + sizeof (NewProp));
188   if (NewList == NULL) {
189     DEBUG ((DEBUG_ERROR, "%a: failed to allocate memory\n", __FUNCTION__));
190     return EFI_OUT_OF_RESOURCES;;
191   }
192   CopyMem (NewList, List, ListSize);
193   CopyMem (&NewList[ListSize], NewProp, sizeof (NewProp));
194 
195   Retval = fdt_setprop (mFdtImage, Node, "compatible", NewList,
196              ListSize + sizeof (NewProp));
197   FreePool (NewList);
198   if (Retval != 0) {
199     DEBUG ((DEBUG_ERROR, "%a: failed to update properties (%d)\n",
200       __FUNCTION__, Retval));
201     return EFI_NOT_FOUND;
202   }
203 
204   return EFI_SUCCESS;
205 }
206 
207 STATIC
208 EFI_STATUS
209 CleanMemoryNodes (
210   VOID
211   )
212 {
213   INTN Node;
214   INT32 Retval;
215 
216   Node = fdt_path_offset (mFdtImage, "/memory");
217   if (Node < 0) {
218     return EFI_SUCCESS;
219   }
220 
221   /*
222    * Remove bogus memory nodes which can make the booted
223    * OS go crazy and ignore the UEFI map.
224    */
225   DEBUG ((DEBUG_INFO, "Removing bogus /memory\n"));
226   Retval = fdt_del_node (mFdtImage, Node);
227   if (Retval != 0) {
228     DEBUG ((DEBUG_ERROR, "Failed to remove /memory\n"));
229     return EFI_NOT_FOUND;
230   }
231 
232   return EFI_SUCCESS;
233 }
234 
235 STATIC
236 EFI_STATUS
237 SanitizePSCI (
238   VOID
239   )
240 {
241   INTN Node;
242   INTN Root;
243   INT32 Retval;
244 
245   Root = fdt_path_offset (mFdtImage, "/");
246   ASSERT (Root >= 0);
247   if (Root < 0) {
248     return EFI_NOT_FOUND;
249   }
250 
251   Node = fdt_path_offset (mFdtImage, "/psci");
252   if (Node < 0) {
253     Node = fdt_add_subnode (mFdtImage, Root, "psci");
254   }
255 
256   ASSERT (Node >= 0);
257   if (Node < 0) {
258     DEBUG ((DEBUG_ERROR, "Couldn't find/create /psci\n"));
259     return EFI_NOT_FOUND;
260   }
261 
262   Retval = fdt_setprop_string (mFdtImage, Node, "compatible", "arm,psci-1.0");
263   if (Retval != 0) {
264     DEBUG ((DEBUG_ERROR, "Couldn't set /psci compatible property\n"));
265     return EFI_NOT_FOUND;
266   }
267 
268   Retval = fdt_setprop_string (mFdtImage, Node, "method", "smc");
269   if (Retval != 0) {
270     DEBUG ((DEBUG_ERROR, "Couldn't set /psci method property\n"));
271     return EFI_NOT_FOUND;
272   }
273 
274   Root = fdt_path_offset (mFdtImage, "/cpus");
275   if (Root < 0) {
276     DEBUG ((DEBUG_ERROR, "No CPUs to update with PSCI enable-method?\n"));
277     return EFI_NOT_FOUND;
278   }
279 
280   Node = fdt_first_subnode (mFdtImage, Root);
281   while (Node >= 0) {
282     if (fdt_setprop_string (mFdtImage, Node, "enable-method", "psci") != 0) {
283       DEBUG ((DEBUG_ERROR, "Failed to update enable-method for a CPU\n"));
284       return EFI_NOT_FOUND;
285     }
286 
287     fdt_delprop (mFdtImage, Node, "cpu-release-addr");
288     Node = fdt_next_subnode (mFdtImage, Node);
289   }
290   return EFI_SUCCESS;
291 }
292 
293 STATIC
294 EFI_STATUS
295 CleanSimpleFramebuffer (
296   VOID
297   )
298 {
299   INTN Node;
300   INT32 Retval;
301 
302   /*
303    * Should look for nodes by kind and remove aliases
304    * by matching against device.
305    */
306   Node = fdt_path_offset (mFdtImage, "display0");
307   if (Node < 0) {
308     return EFI_SUCCESS;
309   }
310 
311   /*
312    * Remove bogus GPU-injected simple-framebuffer, which
313    * doesn't reflect the framebuffer built by UEFI.
314    */
315   DEBUG ((DEBUG_INFO, "Removing bogus display0\n"));
316   Retval = fdt_del_node (mFdtImage, Node);
317   if (Retval != 0) {
318     DEBUG ((DEBUG_ERROR, "Failed to remove display0\n"));
319     return EFI_NOT_FOUND;
320   }
321 
322   Node = fdt_path_offset (mFdtImage, "/aliases");
323   if (Node < 0) {
324     DEBUG ((DEBUG_ERROR, "Couldn't find /aliases to remove display0\n"));
325     return EFI_NOT_FOUND;
326   }
327 
328   Retval = fdt_delprop (mFdtImage, Node, "display0");
329   if (Retval != 0) {
330     DEBUG ((DEBUG_ERROR, "Failed to remove display0 alias\n"));
331     return EFI_NOT_FOUND;
332   }
333 
334   return EFI_SUCCESS;
335 }
336 
337 /**
338   @param  ImageHandle   of the loaded driver
339   @param  SystemTable   Pointer to the System Table
340 
341   @retval EFI_SUCCESS           Protocol registered
342   @retval EFI_OUT_OF_RESOURCES  Cannot allocate protocol data structure
343   @retval EFI_DEVICE_ERROR      Hardware problems
344 
345 **/
346 EFI_STATUS
347 EFIAPI
348 FdtDxeInitialize (
349   IN EFI_HANDLE         ImageHandle,
350   IN EFI_SYSTEM_TABLE   *SystemTable
351   )
352 {
353   INT32      Retval;
354   EFI_STATUS Status;
355   UINTN      FdtSize;
356   VOID       *FdtImage = NULL;
357 
358   if (PcdGet32 (PcdSystemTableMode) != SYSTEM_TABLE_MODE_BOTH &&
359       PcdGet32 (PcdSystemTableMode) != SYSTEM_TABLE_MODE_DT) {
360     DEBUG ((DEBUG_INFO, "Device Tree disabled per user configuration\n"));
361     return EFI_SUCCESS;
362   }
363 
364   Status = gBS->LocateProtocol (&gRaspberryPiFirmwareProtocolGuid, NULL,
365                   (VOID**)&mFwProtocol);
366   ASSERT_EFI_ERROR (Status);
367 
368   FdtImage = (VOID*)(UINTN)PcdGet32 (PcdFdtBaseAddress);
369   Retval = fdt_check_header (FdtImage);
370   if (Retval != 0) {
371     /*
372      * Any one of:
373      * - Invalid config.txt device_tree_address (not PcdFdtBaseAddress)
374      * - Missing FDT for your Pi variant (if not overriding via device_tree=)
375      */
376     DEBUG ((DEBUG_ERROR, "No devicetree passed via config.txt\n"));
377     return EFI_NOT_FOUND;
378   }
379 
380   FdtSize = fdt_totalsize (FdtImage);
381   DEBUG ((DEBUG_INFO, "Devicetree passed via config.txt (0x%lx bytes)\n", FdtSize));
382 
383   /*
384    * Probably overkill.
385    */
386   FdtSize += EFI_PAGE_SIZE * 2;
387   Status = gBS->AllocatePages (AllocateAnyPages, EfiBootServicesData,
388                   EFI_SIZE_TO_PAGES (FdtSize), (EFI_PHYSICAL_ADDRESS*)&mFdtImage);
389   if (EFI_ERROR (Status)) {
390     DEBUG ((DEBUG_ERROR, "Failed to allocate devicetree: %r\n", Status));
391     goto out;
392   }
393 
394   Retval = fdt_open_into (FdtImage, mFdtImage, FdtSize);
395   if (Retval != 0) {
396      DEBUG ((DEBUG_ERROR, "fdt_open_into failed: %d\n", Retval));
397      goto out;
398   }
399 
400   /*
401    * These are all best-effort.
402    */
403 
404   Status = SanitizePSCI ();
405   if (EFI_ERROR (Status)) {
406     Print (L"Failed to sanitize PSCI: %r\n", Status);
407   }
408 
409   Status = CleanMemoryNodes ();
410   if (EFI_ERROR (Status)) {
411     Print (L"Failed to clean memory nodes: %r\n", Status);
412   }
413 
414   Status = CleanSimpleFramebuffer ();
415   if (EFI_ERROR (Status)) {
416     Print (L"Failed to clean frame buffer: %r\n", Status);
417   }
418 
419   Status = FixEthernetAliases ();
420   if (EFI_ERROR (Status)) {
421     Print (L"Failed to fix ethernet aliases: %r\n", Status);
422   }
423 
424   Status = UpdateMacAddress ();
425   if (EFI_ERROR (Status)) {
426     Print (L"Failed to update MAC address: %r\n", Status);
427   }
428 
429   Status = AddUsbCompatibleProperty ();
430   if (EFI_ERROR (Status)) {
431     Print (L"Failed to update USB compatible properties: %r\n", Status);
432   }
433 
434   DEBUG ((DEBUG_INFO, "Installed devicetree at address %p\n", mFdtImage));
435   Status = gBS->InstallConfigurationTable (&gFdtTableGuid, mFdtImage);
436   if (EFI_ERROR (Status)) {
437      DEBUG ((DEBUG_ERROR, "Couldn't register devicetree: %r\n", Status));
438      goto out;
439   }
440 
441 out:
442   if (EFI_ERROR(Status)) {
443     if (mFdtImage != NULL) {
444       gBS->FreePages ((EFI_PHYSICAL_ADDRESS) mFdtImage, EFI_SIZE_TO_PAGES (FdtSize));
445     }
446   }
447   return Status;
448 }
449