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