]> git.proxmox.com Git - mirror_edk2.git/blob - ArmPlatformPkg/FileSystem/BootMonFs/BootMonFsOpenClose.c
778af389a0088cb0bc97d0e0030498e60ed14662
[mirror_edk2.git] / ArmPlatformPkg / FileSystem / BootMonFs / BootMonFsOpenClose.c
1 /** @file
2 *
3 * Copyright (c) 2012-2014, ARM Limited. All rights reserved.
4 *
5 * This program and the accompanying materials
6 * are licensed and made available under the terms and conditions of the BSD License
7 * which accompanies this distribution. The full text of the license may be found at
8 * http://opensource.org/licenses/bsd-license.php
9 *
10 * THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
12 *
13 **/
14
15 #include "BootMonFsInternal.h"
16
17 // Clear a file's image description on storage media:
18 // UEFI allows you to seek past the end of a file, a subsequent write will grow
19 // the file. It does not specify how space between the former end of the file
20 // and the beginning of the write should be filled. It's therefore possible that
21 // BootMonFs metadata, that comes after the end of a file, could be left there
22 // and wrongly detected by BootMonFsImageInBlock.
23 STATIC
24 EFI_STATUS
25 InvalidateImageDescription (
26 IN BOOTMON_FS_FILE *File
27 )
28 {
29 EFI_DISK_IO_PROTOCOL *DiskIo;
30 EFI_BLOCK_IO_PROTOCOL *BlockIo;
31 UINT32 MediaId;
32 UINT32 BlockSize;
33 VOID *Buffer;
34 EFI_STATUS Status;
35 UINT64 DescriptionAddress;
36
37 DiskIo = File->Instance->DiskIo;
38 BlockIo = File->Instance->BlockIo;
39 MediaId = BlockIo->Media->MediaId;
40 BlockSize = BlockIo->Media->BlockSize;
41
42 DescriptionAddress = (File->HwDescription.BlockEnd * BlockSize)
43 - sizeof (HW_IMAGE_DESCRIPTION);
44
45 Buffer = AllocateZeroPool (sizeof (HW_IMAGE_DESCRIPTION));
46
47 Status = DiskIo->WriteDisk (DiskIo,
48 MediaId,
49 DescriptionAddress,
50 sizeof (HW_IMAGE_DESCRIPTION),
51 Buffer
52 );
53
54 FreePool(Buffer);
55
56 return Status;
57 }
58
59 // Flush file data that will extend the file's length. Update and, if necessary,
60 // move the image description.
61 // We need to pass the file's starting position on media (FileStart), because
62 // if the file hasn't been flushed before its Description->BlockStart won't
63 // have been initialised.
64 // FileStart must be aligned to the media's block size.
65 // Note that this function uses DiskIo to flush, so call BlockIo->FlushBlocks()
66 // after calling it.
67 STATIC
68 EFI_STATUS
69 FlushAppendRegion (
70 IN BOOTMON_FS_FILE *File,
71 IN BOOTMON_FS_FILE_REGION *Region,
72 IN UINT64 NewFileSize,
73 IN UINT64 FileStart
74 )
75 {
76 EFI_STATUS Status;
77 EFI_DISK_IO_PROTOCOL *DiskIo;
78 UINTN BlockSize;
79 HW_IMAGE_DESCRIPTION *Description;
80
81 DiskIo = File->Instance->DiskIo;
82
83 BlockSize = File->Instance->BlockIo->Media->BlockSize;
84
85 ASSERT (FileStart % BlockSize == 0);
86
87 // Only invalidate the Image Description of files that have already been
88 // written in Flash
89 if (File->HwDescription.RegionCount > 0) {
90 Status = InvalidateImageDescription (File);
91 ASSERT_EFI_ERROR (Status);
92 }
93
94 //
95 // Update File Description
96 //
97 Description = &File->HwDescription;
98 Description->Attributes = 1;
99 Description->BlockStart = FileStart / BlockSize;
100 Description->BlockEnd = Description->BlockStart + (NewFileSize / BlockSize);
101 Description->Footer.FooterSignature1 = HW_IMAGE_FOOTER_SIGNATURE_1;
102 Description->Footer.FooterSignature2 = HW_IMAGE_FOOTER_SIGNATURE_2;
103 #ifdef MDE_CPU_ARM
104 Description->Footer.Version = HW_IMAGE_FOOTER_VERSION;
105 Description->Footer.Offset = HW_IMAGE_FOOTER_OFFSET;
106 #else
107 Description->Footer.Version = HW_IMAGE_FOOTER_VERSION2;
108 Description->Footer.Offset = HW_IMAGE_FOOTER_OFFSET2;
109 #endif
110 Description->RegionCount = 1;
111 Description->Region[0].Checksum = 0;
112 Description->Region[0].Offset = Description->BlockStart * BlockSize;
113 Description->Region[0].Size = NewFileSize - sizeof (HW_IMAGE_DESCRIPTION);
114
115 Status = BootMonFsComputeFooterChecksum (Description);
116 if (EFI_ERROR (Status)) {
117 return Status;
118 }
119
120 // Write the new file data
121 Status = DiskIo->WriteDisk (
122 DiskIo,
123 File->Instance->Media->MediaId,
124 FileStart + Region->Offset,
125 Region->Size,
126 Region->Buffer
127 );
128 ASSERT_EFI_ERROR (Status);
129
130 // Round the file size up to the nearest block size
131 if ((NewFileSize % BlockSize) > 0) {
132 NewFileSize += BlockSize - (NewFileSize % BlockSize);
133 }
134 // Update the file description on the media
135 Status = DiskIo->WriteDisk (
136 DiskIo,
137 File->Instance->Media->MediaId,
138 (FileStart + NewFileSize) - sizeof (HW_IMAGE_DESCRIPTION),
139 sizeof (HW_IMAGE_DESCRIPTION),
140 Description
141 );
142 ASSERT_EFI_ERROR (Status);
143
144 return Status;
145 }
146
147 BOOLEAN
148 BootMonFsFileNeedFlush (
149 IN BOOTMON_FS_FILE *File
150 )
151 {
152 return !IsListEmpty (&File->RegionToFlushLink);
153 }
154
155 // Find a space on media for a file that has not yet been flushed to disk.
156 // Just returns the first space that's big enough.
157 // This function could easily be adapted to:
158 // - Find space for moving an existing file that has outgrown its space
159 // (We do not currently move files, just return EFI_VOLUME_FULL)
160 // - Find space for a fragment of a file that has outgrown its space
161 // (We do not currently fragment files - it's not clear whether fragmentation
162 // is actually part of BootMonFs as there is no spec)
163 // - Be more clever about finding space (choosing the largest or smallest
164 // suitable space)
165 // Parameters:
166 // File - the new (not yet flushed) file for which we need to find space.
167 // FileStart - the position on media of the file (in bytes).
168 STATIC
169 EFI_STATUS
170 BootMonFsFindSpaceForNewFile (
171 IN BOOTMON_FS_FILE *File,
172 OUT UINT64 *FileStart
173 )
174 {
175 LIST_ENTRY *FileLink;
176 BOOTMON_FS_FILE *RootFile;
177 BOOTMON_FS_FILE *FileEntry;
178 UINTN BlockSize;
179 UINT64 FileSize;
180 EFI_BLOCK_IO_MEDIA *Media;
181
182 Media = File->Instance->BlockIo->Media;
183 BlockSize = Media->BlockSize;
184 RootFile = File->Instance->RootFile;
185
186 if (IsListEmpty (&RootFile->Link)) {
187 return EFI_SUCCESS;
188 }
189
190 // This function must only be called for file which has not been flushed into
191 // Flash yet
192 ASSERT (File->HwDescription.RegionCount == 0);
193
194 // Find out how big the file will be
195 FileSize = BootMonFsGetImageLength (File);
196 // Add the file header to the file
197 FileSize += sizeof (HW_IMAGE_DESCRIPTION);
198
199 *FileStart = 0;
200 // Go through all the files in the list
201 for (FileLink = GetFirstNode (&RootFile->Link);
202 !IsNull (&RootFile->Link, FileLink);
203 FileLink = GetNextNode (&RootFile->Link, FileLink)
204 )
205 {
206 FileEntry = BOOTMON_FS_FILE_FROM_LINK_THIS (FileLink);
207 // If the free space preceding the file is big enough to contain the new
208 // file then use it!
209 if (((FileEntry->HwDescription.BlockStart * BlockSize) - *FileStart)
210 >= FileSize) {
211 // The file list must be in disk-order
212 RemoveEntryList (&File->Link);
213 File->Link.BackLink = FileLink->BackLink;
214 File->Link.ForwardLink = FileLink;
215 FileLink->BackLink->ForwardLink = &File->Link;
216 FileLink->BackLink = &File->Link;
217
218 return EFI_SUCCESS;
219 } else {
220 *FileStart = (FileEntry->HwDescription.BlockEnd + 1) * BlockSize;
221 }
222 }
223 // See if there's space after the last file
224 if ((((Media->LastBlock + 1) * BlockSize) - *FileStart) >= FileSize) {
225 return EFI_SUCCESS;
226 } else {
227 return EFI_VOLUME_FULL;
228 }
229 }
230
231 // Free the resources in the file's Region list.
232 STATIC
233 VOID
234 FreeFileRegions (
235 IN BOOTMON_FS_FILE *File
236 )
237 {
238 LIST_ENTRY *RegionToFlushLink;
239 BOOTMON_FS_FILE_REGION *Region;
240
241 RegionToFlushLink = GetFirstNode (&File->RegionToFlushLink);
242 while (!IsNull (&File->RegionToFlushLink, RegionToFlushLink)) {
243 // Repeatedly remove the first node from the list and free its resources.
244 Region = (BOOTMON_FS_FILE_REGION *) RegionToFlushLink;
245 RemoveEntryList (RegionToFlushLink);
246 FreePool (Region->Buffer);
247 FreePool (Region);
248
249 RegionToFlushLink = GetFirstNode (&File->RegionToFlushLink);
250 }
251 }
252
253 EFIAPI
254 EFI_STATUS
255 BootMonFsFlushFile (
256 IN EFI_FILE_PROTOCOL *This
257 )
258 {
259 EFI_STATUS Status;
260 BOOTMON_FS_INSTANCE *Instance;
261 LIST_ENTRY *RegionToFlushLink;
262 BOOTMON_FS_FILE *File;
263 BOOTMON_FS_FILE *NextFile;
264 BOOTMON_FS_FILE_REGION *Region;
265 LIST_ENTRY *FileLink;
266 UINTN CurrentPhysicalSize;
267 UINTN BlockSize;
268 UINT64 FileStart;
269 UINT64 FileEnd;
270 UINT64 RegionStart;
271 UINT64 RegionEnd;
272 UINT64 NewFileSize;
273 UINT64 EndOfAppendSpace;
274 BOOLEAN HasSpace;
275 EFI_DISK_IO_PROTOCOL *DiskIo;
276 EFI_BLOCK_IO_PROTOCOL *BlockIo;
277
278 Status = EFI_SUCCESS;
279 FileStart = 0;
280
281 File = BOOTMON_FS_FILE_FROM_FILE_THIS (This);
282 if (File == NULL) {
283 return EFI_INVALID_PARAMETER;
284 }
285
286 // Check if the file needs to be flushed
287 if (!BootMonFsFileNeedFlush (File)) {
288 return Status;
289 }
290
291 Instance = File->Instance;
292 BlockIo = Instance->BlockIo;
293 DiskIo = Instance->DiskIo;
294 BlockSize = BlockIo->Media->BlockSize;
295
296 // If the file doesn't exist then find a space for it
297 if (File->HwDescription.RegionCount == 0) {
298 Status = BootMonFsFindSpaceForNewFile (File, &FileStart);
299 // FileStart has changed so we need to recompute RegionEnd
300 if (EFI_ERROR (Status)) {
301 return Status;
302 }
303 } else {
304 FileStart = File->HwDescription.BlockStart * BlockSize;
305 }
306
307 // FileEnd is the NOR address of the end of the file's data
308 FileEnd = FileStart + BootMonFsGetImageLength (File);
309
310 for (RegionToFlushLink = GetFirstNode (&File->RegionToFlushLink);
311 !IsNull (&File->RegionToFlushLink, RegionToFlushLink);
312 RegionToFlushLink = GetNextNode (&File->RegionToFlushLink, RegionToFlushLink)
313 )
314 {
315 Region = (BOOTMON_FS_FILE_REGION*)RegionToFlushLink;
316
317 // RegionStart and RegionEnd are the the intended NOR address of the
318 // start and end of the region
319 RegionStart = FileStart + Region->Offset;
320 RegionEnd = RegionStart + Region->Size;
321
322 if (RegionEnd < FileEnd) {
323 // Handle regions representing edits to existing portions of the file
324 // Write the region data straight into the file
325 Status = DiskIo->WriteDisk (DiskIo,
326 BlockIo->Media->MediaId,
327 RegionStart,
328 Region->Size,
329 Region->Buffer
330 );
331 if (EFI_ERROR (Status)) {
332 return Status;
333 }
334 } else {
335 // Handle regions representing appends to the file
336 //
337 // Note: Since seeking past the end of the file with SetPosition() is
338 // valid, it's possible there will be a gap between the current end of
339 // the file and the beginning of the new region. Since the UEFI spec
340 // says nothing about this case (except "a subsequent write would grow
341 // the file"), we just leave garbage in the gap.
342
343 // Check if there is space to append the new region
344 HasSpace = FALSE;
345 NewFileSize = (RegionEnd - FileStart) + sizeof (HW_IMAGE_DESCRIPTION);
346 CurrentPhysicalSize = BootMonFsGetPhysicalSize (File);
347 if (NewFileSize <= CurrentPhysicalSize) {
348 HasSpace = TRUE;
349 } else {
350 // Get the File Description for the next file
351 FileLink = GetNextNode (&Instance->RootFile->Link, &File->Link);
352 if (!IsNull (&Instance->RootFile->Link, FileLink)) {
353 NextFile = BOOTMON_FS_FILE_FROM_LINK_THIS (FileLink);
354
355 // If there is space between the beginning of the current file and the
356 // beginning of the next file then use it
357 EndOfAppendSpace = NextFile->HwDescription.BlockStart * BlockSize;
358 } else {
359 // We are flushing the last file.
360 EndOfAppendSpace = (BlockIo->Media->LastBlock + 1) * BlockSize;
361 }
362 if (EndOfAppendSpace - FileStart >= NewFileSize) {
363 HasSpace = TRUE;
364 }
365 }
366
367 if (HasSpace == TRUE) {
368 Status = FlushAppendRegion (File, Region, NewFileSize, FileStart);
369 if (EFI_ERROR (Status)) {
370 return Status;
371 }
372 } else {
373 // There isn't a space for the file.
374 // Options here are to move the file or fragment it. However as files
375 // may represent boot images at fixed positions, these options will
376 // break booting if the bootloader doesn't use BootMonFs to find the
377 // image.
378
379 return EFI_VOLUME_FULL;
380 }
381 }
382 }
383
384 FreeFileRegions (File);
385
386 // Flush DiskIo Buffers (see UEFI Spec 12.7 - DiskIo buffers are flushed by
387 // calling FlushBlocks on the same device's BlockIo).
388 BlockIo->FlushBlocks (BlockIo);
389
390 return Status;
391 }
392
393 /**
394 Closes a file on the Nor Flash FS volume.
395
396 @param This The EFI_FILE_PROTOCOL to close.
397
398 @return Always returns EFI_SUCCESS.
399
400 **/
401 EFIAPI
402 EFI_STATUS
403 BootMonFsCloseFile (
404 IN EFI_FILE_PROTOCOL *This
405 )
406 {
407 // Flush the file if needed
408 This->Flush (This);
409 return EFI_SUCCESS;
410 }
411
412 // Create a new instance of BOOTMON_FS_FILE.
413 // Uses BootMonFsCreateFile to
414 STATIC
415 EFI_STATUS
416 CreateNewFile (
417 IN BOOTMON_FS_INSTANCE *Instance,
418 IN CHAR8* AsciiFileName,
419 OUT BOOTMON_FS_FILE **NewHandle
420 )
421 {
422 EFI_STATUS Status;
423 BOOTMON_FS_FILE *File;
424
425 Status = BootMonFsCreateFile (Instance, &File);
426 if (EFI_ERROR (Status)) {
427 return Status;
428 }
429
430 // Remove the leading '\\'
431 if (*AsciiFileName == '\\') {
432 AsciiFileName++;
433 }
434
435 // Set the file name
436 CopyMem (File->HwDescription.Footer.Filename, AsciiFileName, MAX_NAME_LENGTH);
437
438 // Add the file to list of files of the File System
439 InsertHeadList (&Instance->RootFile->Link, &File->Link);
440
441 *NewHandle = File;
442 return Status;
443 }
444
445 /**
446 Opens a file on the Nor Flash FS volume
447
448 Calls BootMonFsGetFileFromAsciiFilename to search the list of tracked files.
449
450 @param This The EFI_FILE_PROTOCOL parent handle.
451 @param NewHandle Double-pointer to the newly created protocol.
452 @param FileName The name of the image/metadata on flash
453 @param OpenMode Read,write,append etc
454 @param Attributes ?
455
456 @return EFI_STATUS
457 OUT_OF_RESOURCES
458 Run out of space to keep track of the allocated structures
459 DEVICE_ERROR
460 Unable to locate the volume associated with the parent file handle
461 NOT_FOUND
462 Filename wasn't found on flash
463 SUCCESS
464
465 **/
466 EFIAPI
467 EFI_STATUS
468 BootMonFsOpenFile (
469 IN EFI_FILE_PROTOCOL *This,
470 OUT EFI_FILE_PROTOCOL **NewHandle,
471 IN CHAR16 *FileName,
472 IN UINT64 OpenMode,
473 IN UINT64 Attributes
474 )
475 {
476 BOOTMON_FS_FILE *Directory;
477 BOOTMON_FS_FILE *File;
478 BOOTMON_FS_INSTANCE *Instance;
479 CHAR8* AsciiFileName;
480 EFI_STATUS Status;
481
482 if ((FileName == NULL) || (NewHandle == NULL)) {
483 return EFI_INVALID_PARAMETER;
484 }
485
486 // The only valid modes are read, read/write, and read/write/create
487 if (!(OpenMode & EFI_FILE_MODE_READ) || ((OpenMode & EFI_FILE_MODE_CREATE) && !(OpenMode & EFI_FILE_MODE_WRITE))) {
488 return EFI_INVALID_PARAMETER;
489 }
490
491 Directory = BOOTMON_FS_FILE_FROM_FILE_THIS (This);
492 if (Directory == NULL) {
493 return EFI_DEVICE_ERROR;
494 }
495
496 Instance = Directory->Instance;
497
498 // If the instance has not been initialized it yet then do it ...
499 if (!Instance->Initialized) {
500 Status = BootMonFsInitialize (Instance);
501 if (EFI_ERROR (Status)) {
502 return Status;
503 }
504 }
505
506 // BootMonFs interface requires ASCII filenames
507 AsciiFileName = AllocatePool ((StrLen (FileName) + 1) * sizeof (CHAR8));
508 if (AsciiFileName == NULL) {
509 return EFI_OUT_OF_RESOURCES;
510 }
511 UnicodeStrToAsciiStr (FileName, AsciiFileName);
512
513 if ((AsciiStrCmp (AsciiFileName, "\\") == 0) ||
514 (AsciiStrCmp (AsciiFileName, "/") == 0) ||
515 (AsciiStrCmp (AsciiFileName, "") == 0) ||
516 (AsciiStrCmp (AsciiFileName, ".") == 0))
517 {
518 //
519 // Opening '/', '\', '.', or the NULL pathname is trying to open the root directory
520 //
521
522 *NewHandle = &Instance->RootFile->File;
523 Instance->RootFile->Position = 0;
524 Status = EFI_SUCCESS;
525 } else {
526 //
527 // Open or Create a regular file
528 //
529
530 // Check if the file already exists
531 Status = BootMonGetFileFromAsciiFileName (Instance, AsciiFileName, &File);
532 if (Status == EFI_NOT_FOUND) {
533 // The file doesn't exist.
534 if (OpenMode & EFI_FILE_MODE_CREATE) {
535 // If the file does not exist but is required then create it.
536 if (Attributes & EFI_FILE_DIRECTORY) {
537 // BootMonFS doesn't support subdirectories
538 Status = EFI_UNSUPPORTED;
539 } else {
540 // Create a new file
541 Status = CreateNewFile (Instance, AsciiFileName, &File);
542 if (!EFI_ERROR (Status)) {
543 File->OpenMode = OpenMode;
544 *NewHandle = &File->File;
545 File->Position = 0;
546 }
547 }
548 }
549 } else if (Status == EFI_SUCCESS) {
550 // The file exists
551 File->OpenMode = OpenMode;
552 *NewHandle = &File->File;
553 File->Position = 0;
554 }
555 }
556
557 FreePool (AsciiFileName);
558
559 return Status;
560 }
561
562 // Delete() for the root directory's EFI_FILE_PROTOCOL instance
563 EFIAPI
564 EFI_STATUS
565 BootMonFsDeleteFail (
566 IN EFI_FILE_PROTOCOL *This
567 )
568 {
569 This->Close(This);
570 // You can't delete the root directory
571 return EFI_WARN_DELETE_FAILURE;
572 }
573 EFIAPI
574 EFI_STATUS
575 BootMonFsDelete (
576 IN EFI_FILE_PROTOCOL *This
577 )
578 {
579 EFI_STATUS Status;
580 BOOTMON_FS_FILE *File;
581 LIST_ENTRY *RegionToFlushLink;
582 BOOTMON_FS_FILE_REGION *Region;
583 HW_IMAGE_DESCRIPTION *Description;
584 EFI_BLOCK_IO_PROTOCOL *BlockIo;
585 UINT8 *EmptyBuffer;
586
587 File = BOOTMON_FS_FILE_FROM_FILE_THIS (This);
588 if (File == NULL) {
589 return EFI_DEVICE_ERROR;
590 }
591
592 Status = EFI_SUCCESS;
593
594 if (BootMonFsFileNeedFlush (File)) {
595 // Free the entries from the Buffer List
596 RegionToFlushLink = GetFirstNode (&File->RegionToFlushLink);
597 do {
598 Region = (BOOTMON_FS_FILE_REGION*)RegionToFlushLink;
599
600 // Get Next entry
601 RegionToFlushLink = RemoveEntryList (RegionToFlushLink);
602
603 // Free the buffers
604 FreePool (Region->Buffer);
605 FreePool (Region);
606 } while (!IsListEmpty (&File->RegionToFlushLink));
607 }
608
609 // If (RegionCount is greater than 0) then the file already exists
610 if (File->HwDescription.RegionCount > 0) {
611 Description = &File->HwDescription;
612 BlockIo = File->Instance->BlockIo;
613
614 // Create an empty buffer
615 EmptyBuffer = AllocateZeroPool (BlockIo->Media->BlockSize);
616 if (EmptyBuffer == NULL) {
617 FreePool (File);
618 return EFI_OUT_OF_RESOURCES;
619 }
620
621 // Invalidate the last Block
622 Status = BlockIo->WriteBlocks (BlockIo, BlockIo->Media->MediaId, Description->BlockEnd, BlockIo->Media->BlockSize, EmptyBuffer);
623 ASSERT_EFI_ERROR (Status);
624
625 FreePool (EmptyBuffer);
626 }
627
628 // Remove the entry from the list
629 RemoveEntryList (&File->Link);
630 FreePool (File);
631 return Status;
632 }
633