]> git.proxmox.com Git - mirror_edk2.git/blob - ArmPlatformPkg/FileSystem/BootMonFs/BootMonFsOpenClose.c
ArmPlatformPkg/BootMonFs: Fix flushing new files
[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 // Skip files that aren't on disk yet
208 if (FileEntry->HwDescription.RegionCount == 0) {
209 continue;
210 }
211
212 // If the free space preceding the file is big enough to contain the new
213 // file then use it!
214 if (((FileEntry->HwDescription.BlockStart * BlockSize) - *FileStart)
215 >= FileSize) {
216 // The file list must be in disk-order
217 RemoveEntryList (&File->Link);
218 File->Link.BackLink = FileLink->BackLink;
219 File->Link.ForwardLink = FileLink;
220 FileLink->BackLink->ForwardLink = &File->Link;
221 FileLink->BackLink = &File->Link;
222
223 return EFI_SUCCESS;
224 } else {
225 *FileStart = (FileEntry->HwDescription.BlockEnd + 1) * BlockSize;
226 }
227 }
228 // See if there's space after the last file
229 if ((((Media->LastBlock + 1) * BlockSize) - *FileStart) >= FileSize) {
230 return EFI_SUCCESS;
231 } else {
232 return EFI_VOLUME_FULL;
233 }
234 }
235
236 // Free the resources in the file's Region list.
237 STATIC
238 VOID
239 FreeFileRegions (
240 IN BOOTMON_FS_FILE *File
241 )
242 {
243 LIST_ENTRY *RegionToFlushLink;
244 BOOTMON_FS_FILE_REGION *Region;
245
246 RegionToFlushLink = GetFirstNode (&File->RegionToFlushLink);
247 while (!IsNull (&File->RegionToFlushLink, RegionToFlushLink)) {
248 // Repeatedly remove the first node from the list and free its resources.
249 Region = (BOOTMON_FS_FILE_REGION *) RegionToFlushLink;
250 RemoveEntryList (RegionToFlushLink);
251 FreePool (Region->Buffer);
252 FreePool (Region);
253
254 RegionToFlushLink = GetFirstNode (&File->RegionToFlushLink);
255 }
256 }
257
258 EFIAPI
259 EFI_STATUS
260 BootMonFsFlushFile (
261 IN EFI_FILE_PROTOCOL *This
262 )
263 {
264 EFI_STATUS Status;
265 BOOTMON_FS_INSTANCE *Instance;
266 LIST_ENTRY *RegionToFlushLink;
267 BOOTMON_FS_FILE *File;
268 BOOTMON_FS_FILE *NextFile;
269 BOOTMON_FS_FILE_REGION *Region;
270 LIST_ENTRY *FileLink;
271 UINTN CurrentPhysicalSize;
272 UINTN BlockSize;
273 UINT64 FileStart;
274 UINT64 FileEnd;
275 UINT64 RegionStart;
276 UINT64 RegionEnd;
277 UINT64 NewFileSize;
278 UINT64 EndOfAppendSpace;
279 BOOLEAN HasSpace;
280 EFI_DISK_IO_PROTOCOL *DiskIo;
281 EFI_BLOCK_IO_PROTOCOL *BlockIo;
282
283 Status = EFI_SUCCESS;
284 FileStart = 0;
285
286 File = BOOTMON_FS_FILE_FROM_FILE_THIS (This);
287 if (File == NULL) {
288 return EFI_INVALID_PARAMETER;
289 }
290
291 // Check if the file needs to be flushed
292 if (!BootMonFsFileNeedFlush (File)) {
293 return Status;
294 }
295
296 Instance = File->Instance;
297 BlockIo = Instance->BlockIo;
298 DiskIo = Instance->DiskIo;
299 BlockSize = BlockIo->Media->BlockSize;
300
301 // If the file doesn't exist then find a space for it
302 if (File->HwDescription.RegionCount == 0) {
303 Status = BootMonFsFindSpaceForNewFile (File, &FileStart);
304 // FileStart has changed so we need to recompute RegionEnd
305 if (EFI_ERROR (Status)) {
306 return Status;
307 }
308 } else {
309 FileStart = File->HwDescription.BlockStart * BlockSize;
310 }
311
312 // FileEnd is the current NOR address of the end of the file's data
313 FileEnd = FileStart + File->HwDescription.Region[0].Size;
314
315 for (RegionToFlushLink = GetFirstNode (&File->RegionToFlushLink);
316 !IsNull (&File->RegionToFlushLink, RegionToFlushLink);
317 RegionToFlushLink = GetNextNode (&File->RegionToFlushLink, RegionToFlushLink)
318 )
319 {
320 Region = (BOOTMON_FS_FILE_REGION*)RegionToFlushLink;
321
322 // RegionStart and RegionEnd are the the intended NOR address of the
323 // start and end of the region
324 RegionStart = FileStart + Region->Offset;
325 RegionEnd = RegionStart + Region->Size;
326
327 if (RegionEnd < FileEnd) {
328 // Handle regions representing edits to existing portions of the file
329 // Write the region data straight into the file
330 Status = DiskIo->WriteDisk (DiskIo,
331 BlockIo->Media->MediaId,
332 RegionStart,
333 Region->Size,
334 Region->Buffer
335 );
336 if (EFI_ERROR (Status)) {
337 return Status;
338 }
339 } else {
340 // Handle regions representing appends to the file
341 //
342 // Note: Since seeking past the end of the file with SetPosition() is
343 // valid, it's possible there will be a gap between the current end of
344 // the file and the beginning of the new region. Since the UEFI spec
345 // says nothing about this case (except "a subsequent write would grow
346 // the file"), we just leave garbage in the gap.
347
348 // Check if there is space to append the new region
349 HasSpace = FALSE;
350 NewFileSize = (RegionEnd - FileStart) + sizeof (HW_IMAGE_DESCRIPTION);
351 CurrentPhysicalSize = BootMonFsGetPhysicalSize (File);
352 if (NewFileSize <= CurrentPhysicalSize) {
353 HasSpace = TRUE;
354 } else {
355 // Get the File Description for the next file
356 FileLink = GetNextNode (&Instance->RootFile->Link, &File->Link);
357 if (!IsNull (&Instance->RootFile->Link, FileLink)) {
358 NextFile = BOOTMON_FS_FILE_FROM_LINK_THIS (FileLink);
359
360 // If there is space between the beginning of the current file and the
361 // beginning of the next file then use it
362 EndOfAppendSpace = NextFile->HwDescription.BlockStart * BlockSize;
363 } else {
364 // We are flushing the last file.
365 EndOfAppendSpace = (BlockIo->Media->LastBlock + 1) * BlockSize;
366 }
367 if (EndOfAppendSpace - FileStart >= NewFileSize) {
368 HasSpace = TRUE;
369 }
370 }
371
372 if (HasSpace == TRUE) {
373 Status = FlushAppendRegion (File, Region, NewFileSize, FileStart);
374 if (EFI_ERROR (Status)) {
375 return Status;
376 }
377 } else {
378 // There isn't a space for the file.
379 // Options here are to move the file or fragment it. However as files
380 // may represent boot images at fixed positions, these options will
381 // break booting if the bootloader doesn't use BootMonFs to find the
382 // image.
383
384 return EFI_VOLUME_FULL;
385 }
386 }
387 }
388
389 FreeFileRegions (File);
390
391 // Flush DiskIo Buffers (see UEFI Spec 12.7 - DiskIo buffers are flushed by
392 // calling FlushBlocks on the same device's BlockIo).
393 BlockIo->FlushBlocks (BlockIo);
394
395 return Status;
396 }
397
398 /**
399 Closes a file on the Nor Flash FS volume.
400
401 @param This The EFI_FILE_PROTOCOL to close.
402
403 @return Always returns EFI_SUCCESS.
404
405 **/
406 EFIAPI
407 EFI_STATUS
408 BootMonFsCloseFile (
409 IN EFI_FILE_PROTOCOL *This
410 )
411 {
412 // Flush the file if needed
413 This->Flush (This);
414 return EFI_SUCCESS;
415 }
416
417 // Create a new instance of BOOTMON_FS_FILE.
418 // Uses BootMonFsCreateFile to
419 STATIC
420 EFI_STATUS
421 CreateNewFile (
422 IN BOOTMON_FS_INSTANCE *Instance,
423 IN CHAR8* AsciiFileName,
424 OUT BOOTMON_FS_FILE **NewHandle
425 )
426 {
427 EFI_STATUS Status;
428 BOOTMON_FS_FILE *File;
429
430 Status = BootMonFsCreateFile (Instance, &File);
431 if (EFI_ERROR (Status)) {
432 return Status;
433 }
434
435 // Remove the leading '\\'
436 if (*AsciiFileName == '\\') {
437 AsciiFileName++;
438 }
439
440 // Set the file name
441 CopyMem (File->HwDescription.Footer.Filename, AsciiFileName, MAX_NAME_LENGTH);
442
443 // Add the file to list of files of the File System
444 InsertHeadList (&Instance->RootFile->Link, &File->Link);
445
446 *NewHandle = File;
447 return Status;
448 }
449
450 /**
451 Opens a file on the Nor Flash FS volume
452
453 Calls BootMonFsGetFileFromAsciiFilename to search the list of tracked files.
454
455 @param This The EFI_FILE_PROTOCOL parent handle.
456 @param NewHandle Double-pointer to the newly created protocol.
457 @param FileName The name of the image/metadata on flash
458 @param OpenMode Read,write,append etc
459 @param Attributes ?
460
461 @return EFI_STATUS
462 OUT_OF_RESOURCES
463 Run out of space to keep track of the allocated structures
464 DEVICE_ERROR
465 Unable to locate the volume associated with the parent file handle
466 NOT_FOUND
467 Filename wasn't found on flash
468 SUCCESS
469
470 **/
471 EFIAPI
472 EFI_STATUS
473 BootMonFsOpenFile (
474 IN EFI_FILE_PROTOCOL *This,
475 OUT EFI_FILE_PROTOCOL **NewHandle,
476 IN CHAR16 *FileName,
477 IN UINT64 OpenMode,
478 IN UINT64 Attributes
479 )
480 {
481 BOOTMON_FS_FILE *Directory;
482 BOOTMON_FS_FILE *File;
483 BOOTMON_FS_INSTANCE *Instance;
484 CHAR8* AsciiFileName;
485 EFI_STATUS Status;
486
487 if ((FileName == NULL) || (NewHandle == NULL)) {
488 return EFI_INVALID_PARAMETER;
489 }
490
491 // The only valid modes are read, read/write, and read/write/create
492 if (!(OpenMode & EFI_FILE_MODE_READ) || ((OpenMode & EFI_FILE_MODE_CREATE) && !(OpenMode & EFI_FILE_MODE_WRITE))) {
493 return EFI_INVALID_PARAMETER;
494 }
495
496 Directory = BOOTMON_FS_FILE_FROM_FILE_THIS (This);
497 if (Directory == NULL) {
498 return EFI_DEVICE_ERROR;
499 }
500
501 Instance = Directory->Instance;
502
503 // If the instance has not been initialized it yet then do it ...
504 if (!Instance->Initialized) {
505 Status = BootMonFsInitialize (Instance);
506 if (EFI_ERROR (Status)) {
507 return Status;
508 }
509 }
510
511 // BootMonFs interface requires ASCII filenames
512 AsciiFileName = AllocatePool ((StrLen (FileName) + 1) * sizeof (CHAR8));
513 if (AsciiFileName == NULL) {
514 return EFI_OUT_OF_RESOURCES;
515 }
516 UnicodeStrToAsciiStr (FileName, AsciiFileName);
517
518 if ((AsciiStrCmp (AsciiFileName, "\\") == 0) ||
519 (AsciiStrCmp (AsciiFileName, "/") == 0) ||
520 (AsciiStrCmp (AsciiFileName, "") == 0) ||
521 (AsciiStrCmp (AsciiFileName, ".") == 0))
522 {
523 //
524 // Opening '/', '\', '.', or the NULL pathname is trying to open the root directory
525 //
526
527 *NewHandle = &Instance->RootFile->File;
528 Instance->RootFile->Position = 0;
529 Status = EFI_SUCCESS;
530 } else {
531 //
532 // Open or Create a regular file
533 //
534
535 // Check if the file already exists
536 Status = BootMonGetFileFromAsciiFileName (Instance, AsciiFileName, &File);
537 if (Status == EFI_NOT_FOUND) {
538 // The file doesn't exist.
539 if (OpenMode & EFI_FILE_MODE_CREATE) {
540 // If the file does not exist but is required then create it.
541 if (Attributes & EFI_FILE_DIRECTORY) {
542 // BootMonFS doesn't support subdirectories
543 Status = EFI_UNSUPPORTED;
544 } else {
545 // Create a new file
546 Status = CreateNewFile (Instance, AsciiFileName, &File);
547 if (!EFI_ERROR (Status)) {
548 File->OpenMode = OpenMode;
549 *NewHandle = &File->File;
550 File->Position = 0;
551 }
552 }
553 }
554 } else if (Status == EFI_SUCCESS) {
555 // The file exists
556 File->OpenMode = OpenMode;
557 *NewHandle = &File->File;
558 File->Position = 0;
559 }
560 }
561
562 FreePool (AsciiFileName);
563
564 return Status;
565 }
566
567 // Delete() for the root directory's EFI_FILE_PROTOCOL instance
568 EFIAPI
569 EFI_STATUS
570 BootMonFsDeleteFail (
571 IN EFI_FILE_PROTOCOL *This
572 )
573 {
574 This->Close(This);
575 // You can't delete the root directory
576 return EFI_WARN_DELETE_FAILURE;
577 }
578 EFIAPI
579 EFI_STATUS
580 BootMonFsDelete (
581 IN EFI_FILE_PROTOCOL *This
582 )
583 {
584 EFI_STATUS Status;
585 BOOTMON_FS_FILE *File;
586 LIST_ENTRY *RegionToFlushLink;
587 BOOTMON_FS_FILE_REGION *Region;
588 HW_IMAGE_DESCRIPTION *Description;
589 EFI_BLOCK_IO_PROTOCOL *BlockIo;
590 UINT8 *EmptyBuffer;
591
592 File = BOOTMON_FS_FILE_FROM_FILE_THIS (This);
593 if (File == NULL) {
594 return EFI_DEVICE_ERROR;
595 }
596
597 Status = EFI_SUCCESS;
598
599 if (BootMonFsFileNeedFlush (File)) {
600 // Free the entries from the Buffer List
601 RegionToFlushLink = GetFirstNode (&File->RegionToFlushLink);
602 do {
603 Region = (BOOTMON_FS_FILE_REGION*)RegionToFlushLink;
604
605 // Get Next entry
606 RegionToFlushLink = RemoveEntryList (RegionToFlushLink);
607
608 // Free the buffers
609 FreePool (Region->Buffer);
610 FreePool (Region);
611 } while (!IsListEmpty (&File->RegionToFlushLink));
612 }
613
614 // If (RegionCount is greater than 0) then the file already exists
615 if (File->HwDescription.RegionCount > 0) {
616 Description = &File->HwDescription;
617 BlockIo = File->Instance->BlockIo;
618
619 // Create an empty buffer
620 EmptyBuffer = AllocateZeroPool (BlockIo->Media->BlockSize);
621 if (EmptyBuffer == NULL) {
622 FreePool (File);
623 return EFI_OUT_OF_RESOURCES;
624 }
625
626 // Invalidate the last Block
627 Status = BlockIo->WriteBlocks (BlockIo, BlockIo->Media->MediaId, Description->BlockEnd, BlockIo->Media->BlockSize, EmptyBuffer);
628 ASSERT_EFI_ERROR (Status);
629
630 FreePool (EmptyBuffer);
631 }
632
633 // Remove the entry from the list
634 RemoveEntryList (&File->Link);
635 FreePool (File);
636 return Status;
637 }
638