1 /** @file
2   Support a Semi Host file system over a debuggers JTAG
3 
4   Copyright (c) 2008 - 2009, Apple Inc. All rights reserved.<BR>
5   Portions copyright (c) 2011 - 2014, ARM Ltd. All rights reserved.<BR>
6 
7   This program and the accompanying materials
8   are licensed and made available under the terms and conditions of the BSD License
9   which accompanies this distribution.  The full text of the license may be found at
10   http://opensource.org/licenses/bsd-license.php
11 
12   THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
13   WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
14 
15 **/
16 
17 #include <Uefi.h>
18 
19 #include <Guid/FileInfo.h>
20 #include <Guid/FileSystemInfo.h>
21 #include <Guid/FileSystemVolumeLabelInfo.h>
22 
23 #include <Library/BaseLib.h>
24 #include <Library/BaseMemoryLib.h>
25 #include <Library/DebugLib.h>
26 #include <Library/MemoryAllocationLib.h>
27 #include <Library/SemihostLib.h>
28 #include <Library/UefiBootServicesTableLib.h>
29 #include <Library/UefiLib.h>
30 
31 #include <Protocol/DevicePath.h>
32 #include <Protocol/SimpleFileSystem.h>
33 
34 #include "SemihostFs.h"
35 
36 #define DEFAULT_SEMIHOST_FS_LABEL   L"SemihostFs"
37 
38 STATIC CHAR16 *mSemihostFsLabel;
39 
40 EFI_SIMPLE_FILE_SYSTEM_PROTOCOL gSemihostFs = {
41   EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_REVISION,
42   VolumeOpen
43 };
44 
45 EFI_FILE gSemihostFsFile = {
46   EFI_FILE_PROTOCOL_REVISION,
47   FileOpen,
48   FileClose,
49   FileDelete,
50   FileRead,
51   FileWrite,
52   FileGetPosition,
53   FileSetPosition,
54   FileGetInfo,
55   FileSetInfo,
56   FileFlush
57 };
58 
59 //
60 // Device path for semi-hosting. It contains our autogened Caller ID GUID.
61 //
62 typedef struct {
63   VENDOR_DEVICE_PATH        Guid;
64   EFI_DEVICE_PATH_PROTOCOL  End;
65 } SEMIHOST_DEVICE_PATH;
66 
67 SEMIHOST_DEVICE_PATH gDevicePath = {
68   {
69     { HARDWARE_DEVICE_PATH, HW_VENDOR_DP, { sizeof (VENDOR_DEVICE_PATH), 0 } },
70     EFI_CALLER_ID_GUID
71   },
72   { END_DEVICE_PATH_TYPE, END_ENTIRE_DEVICE_PATH_SUBTYPE, { sizeof (EFI_DEVICE_PATH_PROTOCOL), 0 } }
73 };
74 
75 typedef struct {
76   LIST_ENTRY    Link;
77   UINT64        Signature;
78   EFI_FILE      File;
79   CHAR8         *FileName;
80   UINT64        OpenMode;
81   UINT32        Position;
82   UINTN         SemihostHandle;
83   BOOLEAN       IsRoot;
84   EFI_FILE_INFO Info;
85 } SEMIHOST_FCB;
86 
87 #define SEMIHOST_FCB_SIGNATURE      SIGNATURE_32( 'S', 'H', 'F', 'C' )
88 #define SEMIHOST_FCB_FROM_THIS(a)   CR(a, SEMIHOST_FCB, File, SEMIHOST_FCB_SIGNATURE)
89 #define SEMIHOST_FCB_FROM_LINK(a)   CR(a, SEMIHOST_FCB, Link, SEMIHOST_FCB_SIGNATURE);
90 
91 EFI_HANDLE  gInstallHandle = NULL;
92 LIST_ENTRY  gFileList = INITIALIZE_LIST_HEAD_VARIABLE (gFileList);
93 
94 SEMIHOST_FCB *
AllocateFCB(VOID)95 AllocateFCB (
96   VOID
97   )
98 {
99   SEMIHOST_FCB *Fcb = AllocateZeroPool (sizeof (SEMIHOST_FCB));
100 
101   if (Fcb != NULL) {
102     CopyMem (&Fcb->File, &gSemihostFsFile, sizeof (gSemihostFsFile));
103     Fcb->Signature = SEMIHOST_FCB_SIGNATURE;
104   }
105 
106   return Fcb;
107 }
108 
109 VOID
FreeFCB(IN SEMIHOST_FCB * Fcb)110 FreeFCB (
111   IN SEMIHOST_FCB *Fcb
112   )
113 {
114   // Remove Fcb from gFileList.
115   RemoveEntryList (&Fcb->Link);
116 
117   // To help debugging...
118   Fcb->Signature = 0;
119 
120   FreePool (Fcb);
121 }
122 
123 
124 
125 EFI_STATUS
VolumeOpen(IN EFI_SIMPLE_FILE_SYSTEM_PROTOCOL * This,OUT EFI_FILE ** Root)126 VolumeOpen (
127   IN  EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *This,
128   OUT EFI_FILE                        **Root
129   )
130 {
131   SEMIHOST_FCB *RootFcb = NULL;
132 
133   if (Root == NULL) {
134     return EFI_INVALID_PARAMETER;
135   }
136 
137   RootFcb = AllocateFCB ();
138   if (RootFcb == NULL) {
139     return EFI_OUT_OF_RESOURCES;
140   }
141 
142   RootFcb->IsRoot = TRUE;
143   RootFcb->Info.Attribute = EFI_FILE_READ_ONLY | EFI_FILE_DIRECTORY;
144 
145   InsertTailList (&gFileList, &RootFcb->Link);
146 
147   *Root = &RootFcb->File;
148 
149   return EFI_SUCCESS;
150 }
151 
152 /**
153   Open a file on the host system by means of the semihosting interface.
154 
155   @param[in]   This        A pointer to the EFI_FILE_PROTOCOL instance that is
156                            the file handle to source location.
157   @param[out]  NewHandle   A pointer to the location to return the opened
158                            handle for the new file.
159   @param[in]   FileName    The Null-terminated string of the name of the file
160                            to be opened.
161   @param[in]   OpenMode    The mode to open the file : Read or Read/Write or
162                            Read/Write/Create
163   @param[in]   Attributes  Only valid for EFI_FILE_MODE_CREATE, in which case these
164                            are the attribute bits for the newly created file. The
165                            mnemonics of the attribute bits are : EFI_FILE_READ_ONLY,
166                            EFI_FILE_HIDDEN, EFI_FILE_SYSTEM, EFI_FILE_RESERVED,
167                            EFI_FILE_DIRECTORY and EFI_FILE_ARCHIVE.
168 
169   @retval  EFI_SUCCESS            The file was open.
170   @retval  EFI_NOT_FOUND          The specified file could not be found.
171   @retval  EFI_DEVICE_ERROR       The last issued semi-hosting operation failed.
172   @retval  EFI_WRITE_PROTECTED    Attempt to create a directory. This is not possible
173                                   with the semi-hosting interface.
174   @retval  EFI_OUT_OF_RESOURCES   Not enough resources were available to open the file.
175   @retval  EFI_INVALID_PARAMETER  At least one of the parameters is invalid.
176 
177 **/
178 EFI_STATUS
FileOpen(IN EFI_FILE * This,OUT EFI_FILE ** NewHandle,IN CHAR16 * FileName,IN UINT64 OpenMode,IN UINT64 Attributes)179 FileOpen (
180   IN  EFI_FILE  *This,
181   OUT EFI_FILE  **NewHandle,
182   IN  CHAR16    *FileName,
183   IN  UINT64    OpenMode,
184   IN  UINT64    Attributes
185   )
186 {
187   SEMIHOST_FCB   *FileFcb;
188   RETURN_STATUS  Return;
189   EFI_STATUS     Status;
190   UINTN          SemihostHandle;
191   CHAR8          *AsciiFileName;
192   UINT32         SemihostMode;
193   UINTN          Length;
194 
195   if ((FileName == NULL) || (NewHandle == NULL)) {
196     return EFI_INVALID_PARAMETER;
197   }
198 
199   if ( (OpenMode != EFI_FILE_MODE_READ) &&
200        (OpenMode != (EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE)) &&
201        (OpenMode != (EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_CREATE)) ) {
202     return EFI_INVALID_PARAMETER;
203   }
204 
205   if ((OpenMode & EFI_FILE_MODE_CREATE) &&
206       (Attributes & EFI_FILE_DIRECTORY)    ) {
207     return EFI_WRITE_PROTECTED;
208   }
209 
210   Length = StrLen (FileName) + 1;
211   AsciiFileName = AllocatePool (Length);
212   if (AsciiFileName == NULL) {
213     return EFI_OUT_OF_RESOURCES;
214   }
215   UnicodeStrToAsciiStrS (FileName, AsciiFileName, Length);
216 
217   // Opening '/', '\', '.', or the NULL pathname is trying to open the root directory
218   if ((AsciiStrCmp (AsciiFileName, "\\") == 0) ||
219       (AsciiStrCmp (AsciiFileName, "/")  == 0) ||
220       (AsciiStrCmp (AsciiFileName, "")   == 0) ||
221       (AsciiStrCmp (AsciiFileName, ".")  == 0)    ) {
222     FreePool (AsciiFileName);
223     return (VolumeOpen (&gSemihostFs, NewHandle));
224   }
225 
226   //
227   // No control is done here concerning the file path. It is passed
228   // as it is to the host operating system through the semi-hosting
229   // interface. We first try to open the file in the read or update
230   // mode even if the file creation has been asked for. That way, if
231   // the file already exists, it is not truncated to zero length. In
232   // write mode (bit SEMIHOST_FILE_MODE_WRITE up), if the file already
233   // exists, it is reset to an empty file.
234   //
235   if (OpenMode == EFI_FILE_MODE_READ) {
236     SemihostMode = SEMIHOST_FILE_MODE_READ | SEMIHOST_FILE_MODE_BINARY;
237   } else {
238     SemihostMode = SEMIHOST_FILE_MODE_READ | SEMIHOST_FILE_MODE_BINARY | SEMIHOST_FILE_MODE_UPDATE;
239   }
240   Return = SemihostFileOpen (AsciiFileName, SemihostMode, &SemihostHandle);
241 
242   if (RETURN_ERROR (Return)) {
243     if (OpenMode & EFI_FILE_MODE_CREATE) {
244       //
245       // In the create if does not exist case, if the opening in update
246       // mode failed, create it and open it in update mode. The update
247       // mode allows for both read and write from and to the file.
248       //
249       Return = SemihostFileOpen (
250                  AsciiFileName,
251                  SEMIHOST_FILE_MODE_WRITE | SEMIHOST_FILE_MODE_BINARY | SEMIHOST_FILE_MODE_UPDATE,
252                  &SemihostHandle
253                  );
254       if (RETURN_ERROR (Return)) {
255         Status = EFI_DEVICE_ERROR;
256         goto Error;
257       }
258     } else {
259       Status = EFI_NOT_FOUND;
260       goto Error;
261     }
262   }
263 
264   // Allocate a control block and fill it
265   FileFcb = AllocateFCB ();
266   if (FileFcb == NULL) {
267     Status = EFI_OUT_OF_RESOURCES;
268     goto Error;
269   }
270 
271   FileFcb->FileName       = AsciiFileName;
272   FileFcb->SemihostHandle = SemihostHandle;
273   FileFcb->Position       = 0;
274   FileFcb->IsRoot         = 0;
275   FileFcb->OpenMode       = OpenMode;
276 
277   Return = SemihostFileLength (SemihostHandle, &Length);
278   if (RETURN_ERROR (Return)) {
279     Status = EFI_DEVICE_ERROR;
280     FreeFCB (FileFcb);
281     goto Error;
282   }
283 
284   FileFcb->Info.FileSize     = Length;
285   FileFcb->Info.PhysicalSize = Length;
286   FileFcb->Info.Attribute    = (OpenMode & EFI_FILE_MODE_CREATE) ? Attributes : 0;
287 
288   InsertTailList (&gFileList, &FileFcb->Link);
289 
290   *NewHandle = &FileFcb->File;
291 
292   return EFI_SUCCESS;
293 
294 Error:
295 
296   FreePool (AsciiFileName);
297 
298   return Status;
299 }
300 
301 /**
302   Worker function that truncate a file specified by its name to a given size.
303 
304   @param[in]  FileName  The Null-terminated string of the name of the file to be opened.
305   @param[in]  Size      The target size for the file.
306 
307   @retval  EFI_SUCCESS       The file was truncated.
308   @retval  EFI_DEVICE_ERROR  The last issued semi-hosting operation failed.
309 
310 **/
311 STATIC
312 EFI_STATUS
TruncateFile(IN CHAR8 * FileName,IN UINTN Size)313 TruncateFile (
314   IN CHAR8  *FileName,
315   IN UINTN   Size
316   )
317 {
318   EFI_STATUS     Status;
319   RETURN_STATUS  Return;
320   UINTN          FileHandle;
321   UINT8          *Buffer;
322   UINTN          Remaining;
323   UINTN          Read;
324   UINTN          ToRead;
325 
326   Status     = EFI_DEVICE_ERROR;
327   FileHandle = 0;
328   Buffer     = NULL;
329 
330   Return = SemihostFileOpen (
331              FileName,
332              SEMIHOST_FILE_MODE_READ | SEMIHOST_FILE_MODE_BINARY,
333              &FileHandle
334              );
335   if (RETURN_ERROR (Return)) {
336     goto Error;
337   }
338 
339   Buffer = AllocatePool (Size);
340   if (Buffer == NULL) {
341     Status = EFI_OUT_OF_RESOURCES;
342     goto Error;
343   }
344 
345   Read = 0;
346   Remaining = Size;
347   while (Remaining > 0) {
348     ToRead = Remaining;
349     Return = SemihostFileRead (FileHandle, &ToRead, Buffer + Read);
350     if (RETURN_ERROR (Return)) {
351       goto Error;
352     }
353     Remaining -= ToRead;
354     Read      += ToRead;
355   }
356 
357   Return = SemihostFileClose (FileHandle);
358   FileHandle = 0;
359   if (RETURN_ERROR (Return)) {
360     goto Error;
361   }
362 
363   Return = SemihostFileOpen (
364              FileName,
365              SEMIHOST_FILE_MODE_WRITE | SEMIHOST_FILE_MODE_BINARY,
366              &FileHandle
367              );
368   if (RETURN_ERROR (Return)) {
369     goto Error;
370   }
371 
372   if (Size > 0) {
373     Return = SemihostFileWrite (FileHandle, &Size, Buffer);
374     if (RETURN_ERROR (Return)) {
375       goto Error;
376     }
377   }
378 
379   Status = EFI_SUCCESS;
380 
381 Error:
382 
383   if (FileHandle != 0) {
384     SemihostFileClose (FileHandle);
385   }
386   if (Buffer != NULL) {
387     FreePool (Buffer);
388   }
389 
390   return (Status);
391 
392 }
393 
394 /**
395   Close a specified file handle.
396 
397   @param[in]  This  A pointer to the EFI_FILE_PROTOCOL instance that is the file
398                     handle to close.
399 
400   @retval  EFI_SUCCESS            The file was closed.
401   @retval  EFI_INVALID_PARAMETER  The parameter "This" is NULL.
402 
403 **/
404 EFI_STATUS
FileClose(IN EFI_FILE * This)405 FileClose (
406   IN EFI_FILE  *This
407   )
408 {
409   SEMIHOST_FCB   *Fcb;
410 
411   if (This == NULL) {
412     return EFI_INVALID_PARAMETER;
413   }
414 
415   Fcb = SEMIHOST_FCB_FROM_THIS(This);
416 
417   if (!Fcb->IsRoot) {
418     SemihostFileClose (Fcb->SemihostHandle);
419     //
420     // The file size might have been reduced from its actual
421     // size on the host file system with FileSetInfo(). In
422     // that case, the file has to be truncated.
423     //
424     if (Fcb->Info.FileSize < Fcb->Info.PhysicalSize) {
425       TruncateFile (Fcb->FileName, Fcb->Info.FileSize);
426     }
427     FreePool (Fcb->FileName);
428   }
429 
430   FreeFCB (Fcb);
431 
432   return EFI_SUCCESS;
433 }
434 
435 /**
436   Close and delete a file.
437 
438   @param[in]  This  A pointer to the EFI_FILE_PROTOCOL instance that is the file
439                     handle to delete.
440 
441   @retval  EFI_SUCCESS              The file was closed and deleted.
442   @retval  EFI_WARN_DELETE_FAILURE  The handle was closed, but the file was not deleted.
443   @retval  EFI_INVALID_PARAMETER    The parameter "This" is NULL.
444 
445 **/
446 EFI_STATUS
FileDelete(IN EFI_FILE * This)447 FileDelete (
448   IN EFI_FILE *This
449   )
450 {
451   SEMIHOST_FCB   *Fcb;
452   RETURN_STATUS  Return;
453   CHAR8          *FileName;
454   UINTN          NameSize;
455 
456   if (This == NULL) {
457     return EFI_INVALID_PARAMETER;
458   }
459 
460   Fcb = SEMIHOST_FCB_FROM_THIS (This);
461 
462   if (!Fcb->IsRoot) {
463     // Get the filename from the Fcb
464     NameSize = AsciiStrLen (Fcb->FileName);
465     FileName = AllocatePool (NameSize + 1);
466 
467     AsciiStrCpyS (FileName, NameSize + 1, Fcb->FileName);
468 
469     // Close the file if it's open.  Disregard return status,
470     // since it might give an error if the file isn't open.
471     This->Close (This);
472 
473     // Call the semihost interface to delete the file.
474     Return = SemihostFileRemove (FileName);
475     if (RETURN_ERROR (Return)) {
476       return EFI_WARN_DELETE_FAILURE;
477     }
478     return EFI_SUCCESS;
479   } else {
480     return EFI_WARN_DELETE_FAILURE;
481   }
482 }
483 
484 /**
485   Read data from an open file.
486 
487   @param[in]      This        A pointer to the EFI_FILE_PROTOCOL instance that
488                               is the file handle to read data from.
489   @param[in out]  BufferSize  On input, the size of the Buffer. On output, the
490                               amount of data returned in Buffer. In both cases,
491                               the size is measured in bytes.
492   @param[out]     Buffer      The buffer into which the data is read.
493 
494   @retval  EFI_SUCCESS            The data was read.
495   @retval  EFI_DEVICE_ERROR       On entry, the current file position is
496                                   beyond the end of the file, or the semi-hosting
497                                   interface reported an error while performing the
498                                   read operation.
499   @retval  EFI_INVALID_PARAMETER  At least one of the three input pointers is NULL.
500 
501 **/
502 EFI_STATUS
FileRead(IN EFI_FILE * This,IN OUT UINTN * BufferSize,OUT VOID * Buffer)503 FileRead (
504   IN     EFI_FILE  *This,
505   IN OUT UINTN     *BufferSize,
506   OUT    VOID      *Buffer
507   )
508 {
509   SEMIHOST_FCB   *Fcb;
510   EFI_STATUS     Status;
511   RETURN_STATUS  Return;
512 
513   if ((This == NULL) || (BufferSize == NULL) || (Buffer == NULL)) {
514     return EFI_INVALID_PARAMETER;
515   }
516 
517   Fcb = SEMIHOST_FCB_FROM_THIS (This);
518 
519   if (Fcb->IsRoot) {
520     // The semi-hosting interface does not allow to list files on the host machine.
521     Status = EFI_UNSUPPORTED;
522   } else {
523     Status = EFI_SUCCESS;
524     if (Fcb->Position >= Fcb->Info.FileSize) {
525       *BufferSize = 0;
526       if (Fcb->Position > Fcb->Info.FileSize) {
527         Status = EFI_DEVICE_ERROR;
528       }
529     } else {
530       Return = SemihostFileRead (Fcb->SemihostHandle, BufferSize, Buffer);
531       if (RETURN_ERROR (Return)) {
532         Status = EFI_DEVICE_ERROR;
533       } else {
534         Fcb->Position += *BufferSize;
535       }
536     }
537   }
538 
539   return Status;
540 }
541 
542 /**
543   Worker function that extends the size of an open file.
544 
545   The extension is filled with zeros.
546 
547   @param[in]  Fcb   Internal description of the opened file
548   @param[in]  Size  The number of bytes, the file has to be extended.
549 
550   @retval  EFI_SUCCESS       The file was extended.
551   @retval  EFI_DEVICE_ERROR  The last issued semi-hosting operation failed.
552 
553 **/
554 STATIC
555 EFI_STATUS
ExtendFile(IN SEMIHOST_FCB * Fcb,IN UINTN Size)556 ExtendFile (
557   IN  SEMIHOST_FCB  *Fcb,
558   IN  UINTN         Size
559   )
560 {
561   RETURN_STATUS  Return;
562   UINTN          Remaining;
563   CHAR8          WriteBuffer[128];
564   UINTN          WriteNb;
565   UINTN          WriteSize;
566 
567   Return = SemihostFileSeek (Fcb->SemihostHandle, Fcb->Info.FileSize);
568   if (RETURN_ERROR (Return)) {
569     return EFI_DEVICE_ERROR;
570   }
571 
572   Remaining = Size;
573   SetMem (WriteBuffer, 0, sizeof(WriteBuffer));
574   while (Remaining > 0) {
575     WriteNb = MIN (Remaining, sizeof(WriteBuffer));
576     WriteSize = WriteNb;
577     Return = SemihostFileWrite (Fcb->SemihostHandle, &WriteSize, WriteBuffer);
578     if (RETURN_ERROR (Return)) {
579       return EFI_DEVICE_ERROR;
580     }
581     Remaining -= WriteNb;
582   }
583 
584   return EFI_SUCCESS;
585 }
586 
587 /**
588   Write data to an open file.
589 
590   @param[in]      This        A pointer to the EFI_FILE_PROTOCOL instance that
591                               is the file handle to write data to.
592   @param[in out]  BufferSize  On input, the size of the Buffer. On output, the
593                               size of the data actually written. In both cases,
594                               the size is measured in bytes.
595   @param[in]      Buffer      The buffer of data to write.
596 
597   @retval  EFI_SUCCESS            The data was written.
598   @retval  EFI_ACCESS_DENIED      Attempt to write into a read only file or
599                                   in a file opened in read only mode.
600   @retval  EFI_DEVICE_ERROR       The last issued semi-hosting operation failed.
601   @retval  EFI_INVALID_PARAMETER  At least one of the three input pointers is NULL.
602 
603 **/
604 EFI_STATUS
FileWrite(IN EFI_FILE * This,IN OUT UINTN * BufferSize,IN VOID * Buffer)605 FileWrite (
606   IN     EFI_FILE *This,
607   IN OUT UINTN    *BufferSize,
608   IN     VOID     *Buffer
609   )
610 {
611   SEMIHOST_FCB   *Fcb;
612   EFI_STATUS     Status;
613   UINTN          WriteSize;
614   RETURN_STATUS  Return;
615   UINTN          Length;
616 
617   if ((This == NULL) || (BufferSize == NULL) || (Buffer == NULL)) {
618     return EFI_INVALID_PARAMETER;
619   }
620 
621   Fcb = SEMIHOST_FCB_FROM_THIS (This);
622 
623   // We cannot write a read-only file
624   if ((Fcb->Info.Attribute & EFI_FILE_READ_ONLY)
625       || !(Fcb->OpenMode & EFI_FILE_MODE_WRITE)) {
626     return EFI_ACCESS_DENIED;
627   }
628 
629   //
630   // If the position has been set past the end of the file, first grow the
631   // file from its current size "Fcb->Info.FileSize" to "Fcb->Position"
632   // size, filling the gap with zeros.
633   //
634   if (Fcb->Position > Fcb->Info.FileSize) {
635     Status = ExtendFile (Fcb, Fcb->Position - Fcb->Info.FileSize);
636     if (EFI_ERROR (Status)) {
637       return Status;
638     }
639     Fcb->Info.FileSize = Fcb->Position;
640   }
641 
642   WriteSize = *BufferSize;
643   Return = SemihostFileWrite (Fcb->SemihostHandle, &WriteSize, Buffer);
644   if (RETURN_ERROR (Return)) {
645     return EFI_DEVICE_ERROR;
646   }
647 
648   Fcb->Position += *BufferSize;
649   if (Fcb->Position > Fcb->Info.FileSize) {
650     Fcb->Info.FileSize = Fcb->Position;
651   }
652 
653   Return = SemihostFileLength (Fcb->SemihostHandle, &Length);
654   if (RETURN_ERROR (Return)) {
655     return EFI_DEVICE_ERROR;
656   }
657   Fcb->Info.PhysicalSize = Length;
658 
659   return EFI_SUCCESS;
660 }
661 
662 /**
663   Return a file's current position.
664 
665   @param[in]   This      A pointer to the EFI_FILE_PROTOCOL instance that is
666                          the file handle to get the current position on.
667   @param[out]  Position  The address to return the file's current position value.
668 
669   @retval  EFI_SUCCESS            The position was returned.
670   @retval  EFI_INVALID_PARAMETER  The parameter "This" or "Position" is NULL.
671 
672 **/
673 EFI_STATUS
FileGetPosition(IN EFI_FILE * This,OUT UINT64 * Position)674 FileGetPosition (
675   IN  EFI_FILE    *This,
676   OUT UINT64      *Position
677   )
678 {
679   SEMIHOST_FCB *Fcb;
680 
681   if ((This == NULL) || (Position == NULL)) {
682     return EFI_INVALID_PARAMETER;
683   }
684 
685   Fcb = SEMIHOST_FCB_FROM_THIS(This);
686 
687   *Position = Fcb->Position;
688 
689   return EFI_SUCCESS;
690 }
691 
692 /**
693   Set a file's current position.
694 
695   @param[in]  This      A pointer to the EFI_FILE_PROTOCOL instance that is
696                         the file handle to set the requested position on.
697   @param[in]  Position  The byte position from the start of the file to set.
698 
699   @retval  EFI_SUCCESS       The position was set.
700   @retval  EFI_DEVICE_ERROR  The semi-hosting positionning operation failed.
701   @retval  EFI_UNSUPPORTED   The seek request for nonzero is not valid on open
702                              directories.
703   @retval  EFI_INVALID_PARAMETER  The parameter "This" is NULL.
704 
705 **/
706 EFI_STATUS
FileSetPosition(IN EFI_FILE * This,IN UINT64 Position)707 FileSetPosition (
708   IN EFI_FILE *This,
709   IN UINT64   Position
710   )
711 {
712   SEMIHOST_FCB   *Fcb;
713   RETURN_STATUS  Return;
714 
715   if (This == NULL) {
716     return EFI_INVALID_PARAMETER;
717   }
718 
719   Fcb = SEMIHOST_FCB_FROM_THIS (This);
720 
721   if (Fcb->IsRoot) {
722     if (Position != 0) {
723       return EFI_UNSUPPORTED;
724     }
725   }
726   else {
727     //
728     // UEFI Spec section 12.5:
729     // "Seeking to position 0xFFFFFFFFFFFFFFFF causes the current position to
730     // be set to the end of the file."
731     //
732     if (Position == 0xFFFFFFFFFFFFFFFF) {
733       Position = Fcb->Info.FileSize;
734     }
735     Return = SemihostFileSeek (Fcb->SemihostHandle, MIN (Position, Fcb->Info.FileSize));
736     if (RETURN_ERROR (Return)) {
737       return EFI_DEVICE_ERROR;
738     }
739   }
740 
741   Fcb->Position = Position;
742 
743   return EFI_SUCCESS;
744 }
745 
746 /**
747   Return information about a file.
748 
749   @param[in]      Fcb         A pointer to the description of an open file.
750   @param[in out]  BufferSize  The size, in bytes, of Buffer.
751   @param[out]     Buffer      A pointer to the data buffer to return. Not NULL if
752                               "*BufferSize" is greater than 0.
753 
754   @retval  EFI_SUCCESS            The information was returned.
755   @retval  EFI_BUFFER_TOO_SMALL   The BufferSize is too small to return the information.
756                                   BufferSize has been updated with the size needed to
757                                   complete the request.
758 **/
759 STATIC
760 EFI_STATUS
GetFileInfo(IN SEMIHOST_FCB * Fcb,IN OUT UINTN * BufferSize,OUT VOID * Buffer)761 GetFileInfo (
762   IN     SEMIHOST_FCB  *Fcb,
763   IN OUT UINTN         *BufferSize,
764   OUT    VOID          *Buffer
765   )
766 {
767   EFI_FILE_INFO   *Info = NULL;
768   UINTN           NameSize = 0;
769   UINTN           ResultSize;
770   UINTN           Index;
771 
772   if (Fcb->IsRoot == TRUE) {
773     ResultSize = SIZE_OF_EFI_FILE_INFO + sizeof(CHAR16);
774   } else {
775     NameSize   = AsciiStrLen (Fcb->FileName) + 1;
776     ResultSize = SIZE_OF_EFI_FILE_INFO + NameSize * sizeof (CHAR16);
777   }
778 
779   if (*BufferSize < ResultSize) {
780     *BufferSize = ResultSize;
781     return EFI_BUFFER_TOO_SMALL;
782   }
783 
784   Info = Buffer;
785 
786   // Copy the current file info
787   CopyMem (Info, &Fcb->Info, SIZE_OF_EFI_FILE_INFO);
788 
789   // Fill in the structure
790   Info->Size = ResultSize;
791 
792   if (Fcb->IsRoot == TRUE) {
793     Info->FileName[0]  = L'\0';
794   } else {
795     for (Index = 0; Index < NameSize; Index++) {
796       Info->FileName[Index] = Fcb->FileName[Index];
797     }
798   }
799 
800   *BufferSize = ResultSize;
801 
802   return EFI_SUCCESS;
803 }
804 
805 /**
806   Return information about a file system.
807 
808   @param[in]      Fcb         A pointer to the description of an open file
809                               which belongs to the file system, the information
810                               is requested for.
811   @param[in out]  BufferSize  The size, in bytes, of Buffer.
812   @param[out]     Buffer      A pointer to the data buffer to return. Not NULL if
813                               "*BufferSize" is greater than 0.
814 
815   @retval  EFI_SUCCESS            The information was returned.
816   @retval  EFI_BUFFER_TOO_SMALL   The BufferSize is too small to return the information.
817                                   BufferSize has been updated with the size needed to
818                                   complete the request.
819 
820 **/
821 STATIC
822 EFI_STATUS
GetFilesystemInfo(IN SEMIHOST_FCB * Fcb,IN OUT UINTN * BufferSize,OUT VOID * Buffer)823 GetFilesystemInfo (
824   IN     SEMIHOST_FCB *Fcb,
825   IN OUT UINTN        *BufferSize,
826   OUT    VOID         *Buffer
827   )
828 {
829   EFI_FILE_SYSTEM_INFO  *Info;
830   EFI_STATUS            Status;
831   UINTN                 ResultSize;
832   UINTN                 StringSize;
833 
834   StringSize = StrSize (mSemihostFsLabel);
835   ResultSize = SIZE_OF_EFI_FILE_SYSTEM_INFO + StringSize;
836 
837   if (*BufferSize >= ResultSize) {
838     ZeroMem (Buffer, ResultSize);
839     Status = EFI_SUCCESS;
840 
841     Info = Buffer;
842 
843     Info->Size       = ResultSize;
844     Info->ReadOnly   = FALSE;
845     Info->VolumeSize = 0;
846     Info->FreeSpace  = 0;
847     Info->BlockSize  = 0;
848 
849     CopyMem (Info->VolumeLabel, mSemihostFsLabel, StringSize);
850   } else {
851     Status = EFI_BUFFER_TOO_SMALL;
852   }
853 
854   *BufferSize = ResultSize;
855   return Status;
856 }
857 
858 /**
859   Return information about a file or a file system.
860 
861   @param[in]      This             A pointer to the EFI_FILE_PROTOCOL instance that
862                                    is the file handle the requested information is for.
863   @param[in]      InformationType  The type identifier for the information being requested :
864                                    EFI_FILE_INFO_ID or EFI_FILE_SYSTEM_INFO_ID or
865                                    EFI_FILE_SYSTEM_VOLUME_LABEL_ID
866   @param[in out]  BufferSize       The size, in bytes, of Buffer.
867   @param[out]     Buffer           A pointer to the data buffer to return. The type of the
868                                    data inside the buffer is indicated by InformationType.
869 
870   @retval  EFI_SUCCESS           The information was returned.
871   @retval  EFI_UNSUPPORTED       The InformationType is not known.
872   @retval  EFI_BUFFER_TOO_SMALL  The BufferSize is too small to return the information.
873                                  BufferSize has been updated with the size needed to
874                                  complete the request.
875   @retval  EFI_INVALID_PARAMETER  The parameter "This" or "InformationType" or "BufferSize"
876                                   is NULL or "Buffer" is NULL and "*Buffersize" is greater
877                                   than 0.
878 
879 **/
880 EFI_STATUS
FileGetInfo(IN EFI_FILE * This,IN EFI_GUID * InformationType,IN OUT UINTN * BufferSize,OUT VOID * Buffer)881 FileGetInfo (
882   IN     EFI_FILE  *This,
883   IN     EFI_GUID  *InformationType,
884   IN OUT UINTN     *BufferSize,
885   OUT    VOID      *Buffer
886   )
887 {
888   SEMIHOST_FCB *Fcb;
889   EFI_STATUS   Status;
890   UINTN        ResultSize;
891 
892   if ((This == NULL)                         ||
893       (InformationType == NULL)              ||
894       (BufferSize == NULL)                   ||
895       ((Buffer == NULL) && (*BufferSize > 0))  ) {
896     return EFI_INVALID_PARAMETER;
897   }
898 
899   Fcb = SEMIHOST_FCB_FROM_THIS(This);
900 
901   if (CompareGuid (InformationType, &gEfiFileSystemInfoGuid)) {
902     Status = GetFilesystemInfo (Fcb, BufferSize, Buffer);
903   } else if (CompareGuid (InformationType, &gEfiFileInfoGuid)) {
904     Status = GetFileInfo (Fcb, BufferSize, Buffer);
905   } else if (CompareGuid (InformationType, &gEfiFileSystemVolumeLabelInfoIdGuid)) {
906     ResultSize = StrSize (mSemihostFsLabel);
907 
908     if (*BufferSize >= ResultSize) {
909       CopyMem (Buffer, mSemihostFsLabel, ResultSize);
910       Status = EFI_SUCCESS;
911     } else {
912       Status = EFI_BUFFER_TOO_SMALL;
913     }
914 
915     *BufferSize = ResultSize;
916   } else {
917     Status = EFI_UNSUPPORTED;
918   }
919 
920   return Status;
921 }
922 
923 /**
924   Set information about a file.
925 
926   @param[in]  Fcb   A pointer to the description of the open file.
927   @param[in]  Info  A pointer to the file information to write.
928 
929   @retval  EFI_SUCCESS           The information was set.
930   @retval  EFI_ACCESS_DENIED     An attempt is made to change the name of a file
931                                  to a file that is already present.
932   @retval  EFI_ACCESS_DENIED     An attempt is being made to change the
933                                  EFI_FILE_DIRECTORY Attribute.
934   @retval  EFI_ACCESS_DENIED     The file is a read-only file or has been
935                                  opened in read-only mode and an attempt is
936                                  being made to modify a field other than
937                                  Attribute.
938   @retval  EFI_WRITE_PROTECTED   An attempt is being made to modify a
939                                  read-only attribute.
940   @retval  EFI_DEVICE_ERROR      The last issued semi-hosting operation failed.
941   @retval  EFI_OUT_OF_RESOURCES  A allocation needed to process the request failed.
942 
943 **/
944 STATIC
945 EFI_STATUS
SetFileInfo(IN SEMIHOST_FCB * Fcb,IN EFI_FILE_INFO * Info)946 SetFileInfo (
947   IN  SEMIHOST_FCB   *Fcb,
948   IN  EFI_FILE_INFO  *Info
949   )
950 {
951   EFI_STATUS     Status;
952   RETURN_STATUS  Return;
953   BOOLEAN        FileSizeIsDifferent;
954   BOOLEAN        FileNameIsDifferent;
955   BOOLEAN        ReadOnlyIsDifferent;
956   CHAR8          *AsciiFileName;
957   UINTN          FileSize;
958   UINTN          Length;
959   UINTN          SemihostHandle;
960 
961   //
962   // A directory can not be changed to a file and a file can
963   // not be changed to a directory.
964   //
965   if (((Info->Attribute & EFI_FILE_DIRECTORY) != 0) != Fcb->IsRoot) {
966     return EFI_ACCESS_DENIED;
967   }
968 
969   Length = StrLen (Info->FileName) + 1;
970   AsciiFileName = AllocatePool (Length);
971   if (AsciiFileName == NULL) {
972     return EFI_OUT_OF_RESOURCES;
973   }
974   UnicodeStrToAsciiStrS (Info->FileName, AsciiFileName, Length);
975 
976   FileSizeIsDifferent = (Info->FileSize != Fcb->Info.FileSize);
977   FileNameIsDifferent = (AsciiStrCmp (AsciiFileName, Fcb->FileName) != 0);
978   ReadOnlyIsDifferent = CompareMem (
979                           &Info->CreateTime,
980                           &Fcb->Info.CreateTime,
981                           3 * sizeof (EFI_TIME)
982                           ) != 0;
983 
984   //
985   // For a read-only file or a file opened in read-only mode, only
986   // the Attribute field can be modified. As the root directory is
987   // read-only (i.e. VolumeOpen()), this protects the root directory
988   // description.
989   //
990   if ((Fcb->OpenMode == EFI_FILE_MODE_READ)     ||
991       (Fcb->Info.Attribute & EFI_FILE_READ_ONLY)  ) {
992     if (FileSizeIsDifferent || FileNameIsDifferent || ReadOnlyIsDifferent) {
993       Status = EFI_ACCESS_DENIED;
994       goto Error;
995     }
996   }
997 
998   if (ReadOnlyIsDifferent) {
999     Status = EFI_WRITE_PROTECTED;
1000     goto Error;
1001   }
1002 
1003   Status = EFI_DEVICE_ERROR;
1004 
1005   if (FileSizeIsDifferent) {
1006     FileSize = Info->FileSize;
1007     if (Fcb->Info.FileSize < FileSize) {
1008       Status = ExtendFile (Fcb, FileSize - Fcb->Info.FileSize);
1009       if (EFI_ERROR (Status)) {
1010         goto Error;
1011       }
1012       //
1013       // The read/write position from the host file system point of view
1014       // is at the end of the file. If the position from this module
1015       // point of view is smaller than the new file size, then
1016       // ask the host file system to move to that position.
1017       //
1018       if (Fcb->Position < FileSize) {
1019         FileSetPosition (&Fcb->File, Fcb->Position);
1020       }
1021     }
1022     Fcb->Info.FileSize = FileSize;
1023 
1024     Return = SemihostFileLength (Fcb->SemihostHandle, &Length);
1025     if (RETURN_ERROR (Return)) {
1026       goto Error;
1027     }
1028     Fcb->Info.PhysicalSize = Length;
1029   }
1030 
1031   //
1032   // Note down in RAM the Attribute field but we can not ask
1033   // for its modification to the host file system as the
1034   // semi-host interface does not provide this feature.
1035   //
1036   Fcb->Info.Attribute = Info->Attribute;
1037 
1038   if (FileNameIsDifferent) {
1039     Return = SemihostFileOpen (
1040                AsciiFileName,
1041                SEMIHOST_FILE_MODE_READ | SEMIHOST_FILE_MODE_BINARY,
1042                &SemihostHandle
1043                );
1044     if (!RETURN_ERROR (Return)) {
1045       SemihostFileClose (SemihostHandle);
1046       Status = EFI_ACCESS_DENIED;
1047       goto Error;
1048     }
1049 
1050     Return = SemihostFileRename (Fcb->FileName, AsciiFileName);
1051     if (RETURN_ERROR (Return)) {
1052       goto Error;
1053     }
1054     FreePool (Fcb->FileName);
1055     Fcb->FileName = AsciiFileName;
1056     AsciiFileName = NULL;
1057   }
1058 
1059   Status = EFI_SUCCESS;
1060 
1061 Error:
1062   if (AsciiFileName != NULL) {
1063     FreePool (AsciiFileName);
1064   }
1065 
1066   return Status;
1067 }
1068 
1069 /**
1070   Set information about a file or a file system.
1071 
1072   @param[in]  This             A pointer to the EFI_FILE_PROTOCOL instance that
1073                                is the file handle the information is for.
1074   @param[in]  InformationType  The type identifier for the information being set :
1075                                EFI_FILE_INFO_ID or EFI_FILE_SYSTEM_INFO_ID or
1076                                EFI_FILE_SYSTEM_VOLUME_LABEL_ID
1077   @param[in]  BufferSize       The size, in bytes, of Buffer.
1078   @param[in]  Buffer           A pointer to the data buffer to write. The type of the
1079                                data inside the buffer is indicated by InformationType.
1080 
1081   @retval  EFI_SUCCESS            The information was set.
1082   @retval  EFI_UNSUPPORTED        The InformationType is not known.
1083   @retval  EFI_DEVICE_ERROR       The last issued semi-hosting operation failed.
1084   @retval  EFI_ACCESS_DENIED      An attempt is being made to change the
1085                                   EFI_FILE_DIRECTORY Attribute.
1086   @retval  EFI_ACCESS_DENIED      InformationType is EFI_FILE_INFO_ID and
1087                                   the file is a read-only file or has been
1088                                   opened in read-only mode and an attempt is
1089                                   being made to modify a field other than
1090                                   Attribute.
1091   @retval  EFI_ACCESS_DENIED      An attempt is made to change the name of a file
1092                                   to a file that is already present.
1093   @retval  EFI_WRITE_PROTECTED    An attempt is being made to modify a
1094                                   read-only attribute.
1095   @retval  EFI_BAD_BUFFER_SIZE    The size of the buffer is lower than that indicated by
1096                                   the data inside the buffer.
1097   @retval  EFI_OUT_OF_RESOURCES   An allocation needed to process the request failed.
1098   @retval  EFI_INVALID_PARAMETER  At least one of the parameters is invalid.
1099 
1100 **/
1101 EFI_STATUS
FileSetInfo(IN EFI_FILE * This,IN EFI_GUID * InformationType,IN UINTN BufferSize,IN VOID * Buffer)1102 FileSetInfo (
1103   IN EFI_FILE  *This,
1104   IN EFI_GUID  *InformationType,
1105   IN UINTN     BufferSize,
1106   IN VOID      *Buffer
1107   )
1108 {
1109   SEMIHOST_FCB          *Fcb;
1110   EFI_FILE_INFO         *Info;
1111   EFI_FILE_SYSTEM_INFO  *SystemInfo;
1112   CHAR16                *VolumeLabel;
1113 
1114   if ((This == NULL) || (InformationType == NULL) || (Buffer == NULL)) {
1115     return EFI_INVALID_PARAMETER;
1116   }
1117 
1118   Fcb = SEMIHOST_FCB_FROM_THIS (This);
1119 
1120   if (CompareGuid (InformationType, &gEfiFileInfoGuid)) {
1121     Info = Buffer;
1122     if (Info->Size < (SIZE_OF_EFI_FILE_INFO + StrSize (Info->FileName))) {
1123       return EFI_INVALID_PARAMETER;
1124     }
1125     if (BufferSize < Info->Size) {
1126       return EFI_BAD_BUFFER_SIZE;
1127     }
1128     return SetFileInfo (Fcb, Info);
1129   } else if (CompareGuid (InformationType, &gEfiFileSystemInfoGuid)) {
1130     SystemInfo = Buffer;
1131     if (SystemInfo->Size <
1132         (SIZE_OF_EFI_FILE_SYSTEM_INFO + StrSize (SystemInfo->VolumeLabel))) {
1133       return EFI_INVALID_PARAMETER;
1134     }
1135     if (BufferSize < SystemInfo->Size) {
1136       return EFI_BAD_BUFFER_SIZE;
1137     }
1138     Buffer = SystemInfo->VolumeLabel;
1139 
1140     if (StrSize (Buffer) > 0) {
1141       VolumeLabel = AllocateCopyPool (StrSize (Buffer), Buffer);
1142       if (VolumeLabel != NULL) {
1143         FreePool (mSemihostFsLabel);
1144         mSemihostFsLabel = VolumeLabel;
1145         return EFI_SUCCESS;
1146       } else {
1147         return EFI_OUT_OF_RESOURCES;
1148       }
1149     } else {
1150       return EFI_INVALID_PARAMETER;
1151     }
1152   } else if (!CompareGuid (InformationType, &gEfiFileSystemVolumeLabelInfoIdGuid)) {
1153     return EFI_UNSUPPORTED;
1154   } else {
1155     return EFI_UNSUPPORTED;
1156   }
1157 }
1158 
1159 EFI_STATUS
FileFlush(IN EFI_FILE * File)1160 FileFlush (
1161   IN EFI_FILE *File
1162   )
1163 {
1164   SEMIHOST_FCB *Fcb;
1165 
1166   Fcb = SEMIHOST_FCB_FROM_THIS(File);
1167 
1168   if (Fcb->IsRoot) {
1169     return EFI_SUCCESS;
1170   } else {
1171     if ((Fcb->Info.Attribute & EFI_FILE_READ_ONLY)
1172         || !(Fcb->OpenMode & EFI_FILE_MODE_WRITE)) {
1173       return EFI_ACCESS_DENIED;
1174     } else {
1175       return EFI_SUCCESS;
1176     }
1177   }
1178 }
1179 
1180 EFI_STATUS
SemihostFsEntryPoint(IN EFI_HANDLE ImageHandle,IN EFI_SYSTEM_TABLE * SystemTable)1181 SemihostFsEntryPoint (
1182   IN EFI_HANDLE           ImageHandle,
1183   IN EFI_SYSTEM_TABLE     *SystemTable
1184   )
1185 {
1186   EFI_STATUS    Status;
1187 
1188   Status = EFI_NOT_FOUND;
1189 
1190   if (SemihostConnectionSupported ()) {
1191     mSemihostFsLabel = AllocateCopyPool (StrSize (DEFAULT_SEMIHOST_FS_LABEL), DEFAULT_SEMIHOST_FS_LABEL);
1192     if (mSemihostFsLabel == NULL) {
1193       return EFI_OUT_OF_RESOURCES;
1194     }
1195 
1196     Status = gBS->InstallMultipleProtocolInterfaces (
1197                     &gInstallHandle,
1198                     &gEfiSimpleFileSystemProtocolGuid, &gSemihostFs,
1199                     &gEfiDevicePathProtocolGuid,       &gDevicePath,
1200                     NULL
1201                     );
1202 
1203     if (EFI_ERROR(Status)) {
1204       FreePool (mSemihostFsLabel);
1205     }
1206   }
1207 
1208   return Status;
1209 }
1210