summaryrefslogtreecommitdiff
path: root/ArmPlatformPkg/ArmJunoPkg/Drivers/ArmJunoDxe/InstallFdt.c
blob: a4220a1895c44f7aee054f26c525050f0bd4fa9d (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
/** @file
*
*  Copyright (c) 2014, ARM Limited. All rights reserved.
*
*  This program and the accompanying materials
*  are licensed and made available under the terms and conditions of the BSD License
*  which accompanies this distribution.  The full text of the license may be found at
*  http://opensource.org/licenses/bsd-license.php
*
*  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
*  WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
*
**/

#include "ArmJunoDxeInternal.h"

#include <Protocol/BlockIo.h>
#include <Protocol/DevicePathFromText.h>
#include <Protocol/DriverBinding.h>
#include <Protocol/SimpleFileSystem.h>

#include <Library/BaseMemoryLib.h>
#include <Library/BdsLib.h>
#include <Library/DevicePathLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/PrintLib.h>
#include <Library/SerialPortLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>

#include <Guid/ArmGlobalVariableHob.h>
#include <Guid/EventGroup.h>
#include <Guid/Fdt.h>
#include <Guid/FileInfo.h>

#include <libfdt.h>

#define FDT_DEFAULT_FILENAME  L"juno"

#define IS_DEVICE_PATH_NODE(node,type,subtype) (((node)->Type == (type)) && ((node)->SubType == (subtype)))

// Hardware Vendor Device Path node for the Juno NOR Flash. We use the Juno NOR Flash if the user
// has not specified another filesystem location into the UEFI Variable 'Fdt'.
// The Juno NOR Flash has its own filesystem format (supported by ArmPlatformPkg/FileSystem/BootMonFs).
STATIC CONST struct {
  VENDOR_DEVICE_PATH NorGuid;
  EFI_DEVICE_PATH    End;
} mJunoNorFlashDevicePath = {
  {
    { HARDWARE_DEVICE_PATH, HW_VENDOR_DP, { sizeof (VENDOR_DEVICE_PATH), 0 } },
    {0xE7223039, 0x5836, 0x41E1, { 0xB5, 0x42, 0xD7, 0xEC, 0x73, 0x6C, 0x5E, 0x59} }
  },
  { END_DEVICE_PATH_TYPE, END_ENTIRE_DEVICE_PATH_SUBTYPE, { sizeof (EFI_DEVICE_PATH_PROTOCOL), 0 } }
};

STATIC EFI_DEVICE_PATH* mFdtFileSystemDevicePath = NULL;
STATIC CHAR16* mFdtFileName = NULL;

STATIC BOOLEAN mFdtTableInstalled = FALSE;

/**
  See definition EFI_DRIVER_BINDING_PROTOCOL.Supported()
**/
EFI_STATUS
EFIAPI
JunoFdtSupported (
  IN        EFI_DRIVER_BINDING_PROTOCOL *DriverBinding,
  IN        EFI_HANDLE                   ControllerHandle,
  IN        EFI_DEVICE_PATH_PROTOCOL    *RemainingDevicePath OPTIONAL
  )
{
  EFI_STATUS                Status;
  EFI_DEVICE_PATH_PROTOCOL *DevicePath;

  //
  // Check if the Handle support the Simple File System Protocol
  //
  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gEfiSimpleFileSystemProtocolGuid,
                  NULL,
                  gImageHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_TEST_PROTOCOL
                  );

  if (EFI_ERROR (Status)) {
    return Status;
  }

  // Check if a DevicePath is attached to the handle
  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gEfiDevicePathProtocolGuid,
                  (VOID **)&DevicePath,
                  gImageHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  // Check if the Device Path is the one from the NOR Flash
  if (CompareMem (mFdtFileSystemDevicePath, DevicePath, GetDevicePathSize (mFdtFileSystemDevicePath)) != 0) {
    return EFI_NOT_FOUND;
  }

  gBS->CloseProtocol (ControllerHandle, &gEfiDevicePathProtocolGuid, gImageHandle, ControllerHandle);
  return Status;
}

/**
  This function is used to print messages back to the user.

  We use the Serial terminal for these messages as the gST->ConOut might not be initialized at this stage.

  @param Message    Message to display to the user
**/
STATIC
VOID
PrintMessage (
  IN CHAR8* Message,
  ...
  )
{
  UINTN   CharCount;
  CHAR8   Buffer[100];
  VA_LIST Marker;

  VA_START (Marker, Message);
  CharCount = AsciiVSPrint (Buffer, sizeof (Buffer), Message, Marker);
  VA_END (Marker);

  SerialPortWrite ((UINT8*)Buffer, CharCount);
}

/**
  See definition EFI_DRIVER_BINDING_PROTOCOL.Start ()
**/
EFI_STATUS
EFIAPI
JunoFdtStart (
  IN        EFI_DRIVER_BINDING_PROTOCOL *DriverBinding,
  IN        EFI_HANDLE                   ControllerHandle,
  IN        EFI_DEVICE_PATH_PROTOCOL    *DevicePath OPTIONAL
  )
{
  EFI_STATUS                       Status;
  EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *BootMonFs;
  EFI_FILE_PROTOCOL               *Fs;
  EFI_FILE_PROTOCOL               *File;
  UINTN                            Size;
  EFI_PHYSICAL_ADDRESS             FdtBlob;
  EFI_FILE_INFO                   *FileInfo;

  if (mFdtTableInstalled) {
    return EFI_ALREADY_STARTED;
  }

  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gEfiSimpleFileSystemProtocolGuid,
                  (VOID**)&BootMonFs,
                  gImageHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );

  if (EFI_ERROR (Status)) {
    return Status;
  }

  // Try to Open the volume and get root directory
  Status = BootMonFs->OpenVolume (BootMonFs, &Fs);
  if (EFI_ERROR (Status)) {
    PrintMessage ("Warning: Fail to open file system that should contain FDT file.\n");
    goto UNLOAD_PROTOCOL;
  }

  File = NULL;
  Status = Fs->Open (Fs, &File, mFdtFileName, EFI_FILE_MODE_READ, 0);
  if (EFI_ERROR (Status)) {
    PrintMessage ("Warning: Fail to load FDT file '%s'.\n", mFdtFileName);
    goto UNLOAD_PROTOCOL;
  }

  Size = 0;
  File->GetInfo (File, &gEfiFileInfoGuid, &Size, NULL);
  FileInfo = AllocatePool (Size);
  Status = File->GetInfo (File, &gEfiFileInfoGuid, &Size, FileInfo);
  if (EFI_ERROR (Status)) {
    goto UNLOAD_PROTOCOL;
  }

  // Get the file size
  Size = FileInfo->FileSize;
  FreePool (FileInfo);

  // The FDT blob is attached to the Configuration Table. It is better to load it as Runtime Service Data
  // to prevent the kernel to overwrite its data
  Status = gBS->AllocatePages (AllocateAnyPages, EfiRuntimeServicesData, EFI_SIZE_TO_PAGES (Size), &FdtBlob);
  if (!EFI_ERROR (Status)) {
    Status = File->Read (File, &Size, (VOID*)(UINTN)(FdtBlob));
    if (EFI_ERROR (Status)) {
      gBS->FreePages (FdtBlob, EFI_SIZE_TO_PAGES (Size));
    } else {
      // Check the FDT header is valid. We only make this check in DEBUG mode in case the FDT header change on
      // production device and this ASSERT() becomes not valid.
      ASSERT (fdt_check_header ((VOID*)(UINTN)(FdtBlob)) == 0);

      // Ensure the Size of the Device Tree is smaller than the size of the read file
      ASSERT ((UINTN)fdt_totalsize ((VOID*)(UINTN)FdtBlob) <= Size);

      // Install the FDT into the Configuration Table
      Status = gBS->InstallConfigurationTable (&gFdtTableGuid, (VOID*)(UINTN)(FdtBlob));
      if (!EFI_ERROR (Status)) {
        mFdtTableInstalled = TRUE;
      }
    }
  }

UNLOAD_PROTOCOL:
  // We do not need the FileSystem protocol
  gBS->CloseProtocol (
      ControllerHandle,
      &gEfiSimpleFileSystemProtocolGuid,
      DriverBinding->ImageHandle,
      ControllerHandle);

  return Status;
}

/**
  See definition EFI_DRIVER_BINDING_PROTOCOL.Stop()
**/
EFI_STATUS
EFIAPI
JunoFdtStop (
  IN        EFI_DRIVER_BINDING_PROTOCOL *DriverBinding,
  IN        EFI_HANDLE                   ControllerHandle,
  IN        UINTN                        NumberOfChildren,
  IN        EFI_HANDLE                  *ChildHandleBuffer OPTIONAL
  )
{
  UINTN      Index;
  VOID*      FdtBlob;
  UINTN      FdtSize;

  // Look for FDT Table
  for (Index = 0; Index < gST->NumberOfTableEntries; Index++) {
    // Check for correct GUID type
    if (CompareGuid (&gFdtTableGuid, &(gST->ConfigurationTable[Index].VendorGuid))) {
      FdtBlob = gST->ConfigurationTable[Index].VendorTable;
      FdtSize = (UINTN)fdt_totalsize (FdtBlob);

      // Uninstall the FDT Configuration Table
      gBS->InstallConfigurationTable (&gFdtTableGuid, NULL);

      // Free the memory
      gBS->FreePages ((EFI_PHYSICAL_ADDRESS)(UINTN)FdtBlob, EFI_SIZE_TO_PAGES (FdtSize));

      return EFI_SUCCESS;
    }
  }

  return EFI_NOT_FOUND;
}

//
// Driver Binding Protocol for Juno FDT support
//
EFI_DRIVER_BINDING_PROTOCOL mJunoFdtBinding = {
  JunoFdtSupported,
  JunoFdtStart,
  JunoFdtStop,
  0xa,
  NULL,
  NULL
};

/**
  Notification function of EFI_END_OF_DXE_EVENT_GROUP_GUID event group.

  This is a notification function registered on EFI_END_OF_DXE_EVENT_GROUP_GUID event group.

  @param  Event        Event whose notification function is being invoked.
  @param  Context      Pointer to the notification function's context.

**/
STATIC
VOID
EFIAPI
OnEndOfDxe (
  EFI_EVENT                               Event,
  VOID                                    *Context
  )
{
  EFI_DEVICE_PATH *DevicePathNode;
  EFI_HANDLE       Handle;
  EFI_STATUS       Status;
  UINTN            VariableSize;
  CHAR16*          FdtDevicePathStr;
  EFI_DEVICE_PATH_FROM_TEXT_PROTOCOL  *EfiDevicePathFromTextProtocol;

  //
  // Read the 'FDT' UEFI Variable to know where we should we read the blob from.
  // The 'Fdt' variable contains either the full device path or only the filename of the FDT.
  // If 'Fdt' only contains the filename then we assume its location is on the NOR Flash.
  //
  VariableSize     = 0;
  Status = gRT->GetVariable (L"Fdt", &gArmGlobalVariableGuid, NULL, &VariableSize, mFdtFileSystemDevicePath);
  if (Status == EFI_BUFFER_TOO_SMALL) {
    // Get the environment variable value
    mFdtFileSystemDevicePath = AllocatePool (VariableSize);
    if (mFdtFileSystemDevicePath != NULL) {
      Status = gRT->GetVariable (L"Fdt", &gArmGlobalVariableGuid, NULL, &VariableSize, mFdtFileSystemDevicePath);
      if (EFI_ERROR (Status)) {
        FreePool (mFdtFileSystemDevicePath);
        ASSERT_EFI_ERROR (Status);
        return;
      }
    } else {
      ASSERT_EFI_ERROR (EFI_OUT_OF_RESOURCES);
      return;
    }
  } else if (Status == EFI_NOT_FOUND) {
    // If the 'Fdt' variable does not exist then we get the FDT location from the PCD
    FdtDevicePathStr = (CHAR16*)PcdGetPtr (PcdFdtDevicePath);

    Status = gBS->LocateProtocol (&gEfiDevicePathFromTextProtocolGuid, NULL, (VOID **)&EfiDevicePathFromTextProtocol);
    if (EFI_ERROR (Status)) {
      ASSERT_EFI_ERROR (Status);
      return;
    }

    // Conversion of the Device Path string into EFI Device Path
    mFdtFileSystemDevicePath = EfiDevicePathFromTextProtocol->ConvertTextToDevicePath (FdtDevicePathStr);
  }

  if (mFdtFileSystemDevicePath != NULL) {
    // Look for the FDT filename that should be contained into the FilePath device path node
    DevicePathNode = mFdtFileSystemDevicePath;
    while (!IsDevicePathEnd (DevicePathNode)) {
      if (IS_DEVICE_PATH_NODE (DevicePathNode, MEDIA_DEVICE_PATH, MEDIA_FILEPATH_DP)) {
        // Extract the name from the File Path Node. The name of the Filename is the size of the
        // device path node minus the size of the device path node header.
        mFdtFileName = AllocateCopyPool (
            DevicePathNodeLength (DevicePathNode) - sizeof(EFI_DEVICE_PATH_PROTOCOL),
            ((FILEPATH_DEVICE_PATH*)DevicePathNode)->PathName);
        if (mFdtFileName == NULL) {
          ASSERT_EFI_ERROR (EFI_OUT_OF_RESOURCES);
          return;
        }

        // We remove the FilePath device path node from the FileSystem Device Path
        // because it will never match a device path installed by the FileSystem driver
        SetDevicePathEndNode (DevicePathNode);
        break;
      }
      DevicePathNode = NextDevicePathNode (DevicePathNode);
    }

    // The UEFI Variable might just contain the FDT filename. In this case we assume the FileSystem is
    // the NOR Flash based one (ie: BootMonFs).
    // If it was only containing the FilePath device node then the previous condition should have
    // replaced it by the End Device Path Node.
    if (IsDevicePathEndType (mFdtFileSystemDevicePath)) {
      mFdtFileSystemDevicePath = (EFI_DEVICE_PATH*)&mJunoNorFlashDevicePath;
    }
  } else {
    // Fallback on the NOR Flash filesystem
    mFdtFileSystemDevicePath = (EFI_DEVICE_PATH*)&mJunoNorFlashDevicePath;
  }

  // If the FDT FileName has been provided during the FileSystem identification
  if (mFdtFileName == NULL) {
    mFdtFileName = AllocateCopyPool (StrSize (FDT_DEFAULT_FILENAME), FDT_DEFAULT_FILENAME);
    if (mFdtFileName == NULL) {
      ASSERT_EFI_ERROR (Status);
      return;
    }
  }

  // Install the Binding protocol to verify when the FileSystem that contains the FDT has been installed
  Status = gBS->InstallMultipleProtocolInterfaces (
                  &gImageHandle,
                  &gEfiDriverBindingProtocolGuid, &mJunoFdtBinding,
                  NULL
                  );
  if (EFI_ERROR (Status)) {
    ASSERT_EFI_ERROR (Status);
    return;
  }

  //
  // Force to connect the FileSystem that contains the FDT
  //
  BdsConnectDevicePath (mFdtFileSystemDevicePath, &Handle, NULL);
}

EFI_STATUS
JunoFdtInstall (
  IN EFI_HANDLE                            ImageHandle
  )
{
  EFI_STATUS Status;
  EFI_EVENT  EndOfDxeEvent;

  // Register the event handling function to set the End Of DXE flag.
  // We wait until the end of the DXE phase to load the FDT to make sure
  // all the required drivers (NOR Flash, UEFI Variable, BootMonFs) are dispatched
  Status = gBS->CreateEventEx (
                  EVT_NOTIFY_SIGNAL,
                  TPL_CALLBACK,
                  OnEndOfDxe,
                  NULL,
                  &gEfiEndOfDxeEventGroupGuid,
                  &EndOfDxeEvent
                  );
  ASSERT_EFI_ERROR (Status);

  return Status;
}