]> git.proxmox.com Git - mirror_edk2.git/blobdiff - OvmfPkg/Library/PlatformInitLib/Platform.c
UefiCpuPkg: Move AsmRelocateApLoopStart from Mpfuncs.nasm to AmdSev.nasm
[mirror_edk2.git] / OvmfPkg / Library / PlatformInitLib / Platform.c
index 60a30a01f3b51c500913dc600ab18df9cb8fe8e1..9fee6e481038647d0140a66eb331c568a1f63736 100644 (file)
 #include <IndustryStandard/Pci22.h>\r
 #include <IndustryStandard/Q35MchIch9.h>\r
 #include <IndustryStandard/QemuCpuHotplug.h>\r
+#include <Library/MemoryAllocationLib.h>\r
 #include <Library/QemuFwCfgLib.h>\r
 #include <Library/QemuFwCfgS3Lib.h>\r
 #include <Library/QemuFwCfgSimpleParserLib.h>\r
 #include <Library/PciLib.h>\r
+#include <Guid/SystemNvDataGuid.h>\r
+#include <Guid/VariableFormat.h>\r
 #include <OvmfPlatforms.h>\r
 \r
 #include <Library/PlatformInitLib.h>\r
 \r
+#define CPUHP_BUGCHECK_OVERRIDE_FWCFG_FILE \\r
+  "opt/org.tianocore/X-Cpuhp-Bugcheck-Override"\r
+\r
 VOID\r
 EFIAPI\r
 PlatformAddIoMemoryBaseSizeHob (\r
@@ -125,7 +131,6 @@ PlatformMemMapInitialization (
 {\r
   UINT64  PciIoBase;\r
   UINT64  PciIoSize;\r
-  UINT32  TopOfLowRam;\r
   UINT64  PciExBarBase;\r
   UINT32  PciBase;\r
   UINT32  PciSize;\r
@@ -147,7 +152,7 @@ PlatformMemMapInitialization (
     return;\r
   }\r
 \r
-  TopOfLowRam  = PlatformGetSystemMemorySizeBelow4gb (PlatformInfoHob);\r
+  PlatformGetSystemMemorySizeBelow4gb (PlatformInfoHob);\r
   PciExBarBase = 0;\r
   if (PlatformInfoHob->HostBridgeDevId == INTEL_Q35_MCH_DEVICE_ID) {\r
     //\r
@@ -155,11 +160,11 @@ PlatformMemMapInitialization (
     // the base of the 32-bit PCI host aperture.\r
     //\r
     PciExBarBase = PcdGet64 (PcdPciExpressBaseAddress);\r
-    ASSERT (TopOfLowRam <= PciExBarBase);\r
+    ASSERT (PlatformInfoHob->LowMemory <= PciExBarBase);\r
     ASSERT (PciExBarBase <= MAX_UINT32 - SIZE_256MB);\r
     PciBase = (UINT32)(PciExBarBase + SIZE_256MB);\r
   } else {\r
-    ASSERT (TopOfLowRam <= PlatformInfoHob->Uc32Base);\r
+    ASSERT (PlatformInfoHob->LowMemory <= PlatformInfoHob->Uc32Base);\r
     PciBase = PlatformInfoHob->Uc32Base;\r
   }\r
 \r
@@ -314,7 +319,9 @@ PlatformMiscInitialization (
   //\r
   // Disable A20 Mask\r
   //\r
-  IoOr8 (0x92, BIT1);\r
+  if (PlatformInfoHob->HostBridgeDevId != CLOUDHV_DEVICE_ID) {\r
+    IoOr8 (0x92, BIT1);\r
+  }\r
 \r
   //\r
   // Build the CPU HOB with guest RAM size dependent address width and 16-bits\r
@@ -400,6 +407,142 @@ PlatformMiscInitialization (
   }\r
 }\r
 \r
+/**\r
+  Check for various QEMU bugs concerning CPU numbers.\r
+\r
+  Compensate for those bugs if various conditions are satisfied, by updating a\r
+  suitable subset of the input-output parameters. The function may not return\r
+  (it may hang deliberately), even in RELEASE builds, if the QEMU bug is\r
+  impossible to cover up.\r
+\r
+  @param[in,out] BootCpuCount  On input, the boot CPU count reported by QEMU via\r
+                               fw_cfg (QemuFwCfgItemSmpCpuCount). The caller is\r
+                               responsible for ensuring (BootCpuCount > 0); that\r
+                               is, if QEMU does not provide the boot CPU count\r
+                               via fw_cfg *at all*, then this function must not\r
+                               be called.\r
+\r
+  @param[in,out] Present       On input, the number of present-at-boot CPUs, as\r
+                               reported by QEMU through the modern CPU hotplug\r
+                               register block.\r
+\r
+  @param[in,out] Possible      On input, the number of possible CPUs, as\r
+                               reported by QEMU through the modern CPU hotplug\r
+                               register block.\r
+**/\r
+STATIC\r
+VOID\r
+PlatformCpuCountBugCheck (\r
+  IN OUT UINT16  *BootCpuCount,\r
+  IN OUT UINT32  *Present,\r
+  IN OUT UINT32  *Possible\r
+  )\r
+{\r
+  ASSERT (*BootCpuCount > 0);\r
+\r
+  //\r
+  // Sanity check: we need at least 1 present CPU (CPU#0 is always present).\r
+  //\r
+  // The legacy-to-modern switching of the CPU hotplug register block got broken\r
+  // (for TCG) in QEMU v5.1.0. Refer to "IO port write width clamping differs\r
+  // between TCG and KVM" at\r
+  // <http://mid.mail-archive.com/aaedee84-d3ed-a4f9-21e7-d221a28d1683@redhat.com>\r
+  // or at\r
+  // <https://lists.gnu.org/archive/html/qemu-devel/2023-01/msg00199.html>.\r
+  //\r
+  // QEMU received the fix in commit dab30fbef389 ("acpi: cpuhp: fix\r
+  // guest-visible maximum access size to the legacy reg block", 2023-01-08), to\r
+  // be included in QEMU v8.0.0.\r
+  //\r
+  // If we're affected by this QEMU bug, then we must not continue: it confuses\r
+  // the multiprocessing in UefiCpuPkg/Library/MpInitLib, and breaks CPU\r
+  // hot(un)plug with SMI in OvmfPkg/CpuHotplugSmm.\r
+  //\r
+  if (*Present == 0) {\r
+    UINTN                      Idx;\r
+    STATIC CONST CHAR8 *CONST  Message[] = {\r
+      "Broken CPU hotplug register block found. Update QEMU to version 8+, or",\r
+      "to a stable release with commit dab30fbef389 backported. Refer to",\r
+      "<https://bugzilla.tianocore.org/show_bug.cgi?id=4250>.",\r
+      "Consequences of the QEMU bug may include, but are not limited to:",\r
+      "- all firmware logic, dependent on the CPU hotplug register block,",\r
+      "  being confused, for example, multiprocessing-related logic;",\r
+      "- guest OS data loss, including filesystem corruption, due to crash or",\r
+      "  hang during ACPI S3 resume;",\r
+      "- SMM privilege escalation, by a malicious guest OS or 3rd partty UEFI",\r
+      "  agent, against the platform firmware.",\r
+      "These symptoms need not necessarily be limited to the QEMU user",\r
+      "attempting to hot(un)plug a CPU.",\r
+      "The firmware will now stop (hang) deliberately, in order to prevent the",\r
+      "above symptoms.",\r
+      "You can forcibly override the hang, *at your own risk*, with the",\r
+      "following *experimental* QEMU command line option:",\r
+      "  -fw_cfg name=" CPUHP_BUGCHECK_OVERRIDE_FWCFG_FILE ",string=yes",\r
+      "Please only report such bugs that you can reproduce *without* the",\r
+      "override.",\r
+    };\r
+    RETURN_STATUS              ParseStatus;\r
+    BOOLEAN                    Override;\r
+\r
+    DEBUG ((\r
+      DEBUG_ERROR,\r
+      "%a: Present=%u Possible=%u\n",\r
+      __FUNCTION__,\r
+      *Present,\r
+      *Possible\r
+      ));\r
+    for (Idx = 0; Idx < ARRAY_SIZE (Message); ++Idx) {\r
+      DEBUG ((DEBUG_ERROR, "%a: %a\n", __FUNCTION__, Message[Idx]));\r
+    }\r
+\r
+    ParseStatus = QemuFwCfgParseBool (\r
+                    CPUHP_BUGCHECK_OVERRIDE_FWCFG_FILE,\r
+                    &Override\r
+                    );\r
+    if (!RETURN_ERROR (ParseStatus) && Override) {\r
+      DEBUG ((\r
+        DEBUG_WARN,\r
+        "%a: \"%a\" active. You've been warned.\n",\r
+        __FUNCTION__,\r
+        CPUHP_BUGCHECK_OVERRIDE_FWCFG_FILE\r
+        ));\r
+      //\r
+      // The bug is in QEMU v5.1.0+, where we're not affected by the QEMU v2.7\r
+      // reset bug, so BootCpuCount from fw_cfg is reliable. Assume a fully\r
+      // populated topology, like when the modern CPU hotplug interface is\r
+      // unavailable.\r
+      //\r
+      *Present  = *BootCpuCount;\r
+      *Possible = *BootCpuCount;\r
+      return;\r
+    }\r
+\r
+    ASSERT (FALSE);\r
+    CpuDeadLoop ();\r
+  }\r
+\r
+  //\r
+  // Sanity check: fw_cfg and the modern CPU hotplug interface should expose the\r
+  // same boot CPU count.\r
+  //\r
+  if (*BootCpuCount != *Present) {\r
+    DEBUG ((\r
+      DEBUG_WARN,\r
+      "%a: QEMU v2.7 reset bug: BootCpuCount=%d Present=%u\n",\r
+      __FUNCTION__,\r
+      *BootCpuCount,\r
+      *Present\r
+      ));\r
+    //\r
+    // The handling of QemuFwCfgItemSmpCpuCount, across CPU hotplug plus\r
+    // platform reset (including S3), was corrected in QEMU commit e3cadac073a9\r
+    // ("pc: fix FW_CFG_NB_CPUS to account for -device added CPUs", 2016-11-16),\r
+    // part of release v2.8.0.\r
+    //\r
+    *BootCpuCount = (UINT16)*Present;\r
+  }\r
+}\r
+\r
 /**\r
   Fetch the boot CPU count and the possible CPU count from QEMU, and expose\r
   them to UefiCpuPkg modules.\r
@@ -410,14 +553,17 @@ PlatformMaxCpuCountInitialization (
   IN OUT EFI_HOB_PLATFORM_INFO  *PlatformInfoHob\r
   )\r
 {\r
-  UINT16  BootCpuCount;\r
+  UINT16  BootCpuCount = 0;\r
   UINT32  MaxCpuCount;\r
 \r
   //\r
   // Try to fetch the boot CPU count.\r
   //\r
-  QemuFwCfgSelectItem (QemuFwCfgItemSmpCpuCount);\r
-  BootCpuCount = QemuFwCfgRead16 ();\r
+  if (QemuFwCfgIsAvailable ()) {\r
+    QemuFwCfgSelectItem (QemuFwCfgItemSmpCpuCount);\r
+    BootCpuCount = QemuFwCfgRead16 ();\r
+  }\r
+\r
   if (BootCpuCount == 0) {\r
     //\r
     // QEMU doesn't report the boot CPU count. (BootCpuCount == 0) will let\r
@@ -511,8 +657,8 @@ PlatformMaxCpuCountInitialization (
         UINT8  CpuStatus;\r
 \r
         //\r
-        // Read the status of the currently selected CPU. This will help with a\r
-        // sanity check against "BootCpuCount".\r
+        // Read the status of the currently selected CPU. This will help with\r
+        // various CPU count sanity checks.\r
         //\r
         CpuStatus = IoRead8 (CpuHpBase + QEMU_CPUHP_R_CPU_STAT);\r
         if ((CpuStatus & QEMU_CPUHP_STAT_ENABLED) != 0) {\r
@@ -533,27 +679,10 @@ PlatformMaxCpuCountInitialization (
         ASSERT (Selected == Possible || Selected == 0);\r
       } while (Selected > 0);\r
 \r
-      //\r
-      // Sanity check: fw_cfg and the modern CPU hotplug interface should\r
-      // return the same boot CPU count.\r
-      //\r
-      if (BootCpuCount != Present) {\r
-        DEBUG ((\r
-          DEBUG_WARN,\r
-          "%a: QEMU v2.7 reset bug: BootCpuCount=%d "\r
-          "Present=%u\n",\r
-          __FUNCTION__,\r
-          BootCpuCount,\r
-          Present\r
-          ));\r
-        //\r
-        // The handling of QemuFwCfgItemSmpCpuCount, across CPU hotplug plus\r
-        // platform reset (including S3), was corrected in QEMU commit\r
-        // e3cadac073a9 ("pc: fix FW_CFG_NB_CPUS to account for -device added\r
-        // CPUs", 2016-11-16), part of release v2.8.0.\r
-        //\r
-        BootCpuCount = (UINT16)Present;\r
-      }\r
+      PlatformCpuCountBugCheck (&BootCpuCount, &Present, &Possible);\r
+      ASSERT (Present > 0);\r
+      ASSERT (Present <= Possible);\r
+      ASSERT (BootCpuCount == Present);\r
 \r
       MaxCpuCount = Possible;\r
     }\r
@@ -571,3 +700,240 @@ PlatformMaxCpuCountInitialization (
   PlatformInfoHob->PcdCpuMaxLogicalProcessorNumber  = MaxCpuCount;\r
   PlatformInfoHob->PcdCpuBootLogicalProcessorNumber = BootCpuCount;\r
 }\r
+\r
+/**\r
+  Check padding data all bit should be 1.\r
+\r
+  @param[in] Buffer     - A pointer to buffer header\r
+  @param[in] BufferSize - Buffer size\r
+\r
+  @retval  TRUE   - The padding data is valid.\r
+  @retval  TRUE  - The padding data is invalid.\r
+\r
+**/\r
+BOOLEAN\r
+CheckPaddingData (\r
+  IN UINT8   *Buffer,\r
+  IN UINT32  BufferSize\r
+  )\r
+{\r
+  UINT32  index;\r
+\r
+  for (index = 0; index < BufferSize; index++) {\r
+    if (Buffer[index] != 0xFF) {\r
+      return FALSE;\r
+    }\r
+  }\r
+\r
+  return TRUE;\r
+}\r
+\r
+/**\r
+  Check the integrity of NvVarStore.\r
+\r
+  @param[in] NvVarStoreBase - A pointer to NvVarStore header\r
+  @param[in] NvVarStoreSize - NvVarStore size\r
+\r
+  @retval  TRUE   - The NvVarStore is valid.\r
+  @retval  FALSE  - The NvVarStore is invalid.\r
+\r
+**/\r
+BOOLEAN\r
+EFIAPI\r
+PlatformValidateNvVarStore (\r
+  IN UINT8   *NvVarStoreBase,\r
+  IN UINT32  NvVarStoreSize\r
+  )\r
+{\r
+  UINT16                         Checksum;\r
+  UINTN                          VariableBase;\r
+  UINT32                         VariableOffset;\r
+  UINT32                         VariableOffsetBeforeAlign;\r
+  EFI_FIRMWARE_VOLUME_HEADER     *NvVarStoreFvHeader;\r
+  VARIABLE_STORE_HEADER          *NvVarStoreHeader;\r
+  AUTHENTICATED_VARIABLE_HEADER  *VariableHeader;\r
+\r
+  static EFI_GUID  FvHdrGUID       = EFI_SYSTEM_NV_DATA_FV_GUID;\r
+  static EFI_GUID  VarStoreHdrGUID = EFI_AUTHENTICATED_VARIABLE_GUID;\r
+\r
+  VariableOffset = 0;\r
+\r
+  if (NvVarStoreBase == NULL) {\r
+    DEBUG ((DEBUG_ERROR, "NvVarStore pointer is NULL.\n"));\r
+    return FALSE;\r
+  }\r
+\r
+  //\r
+  // Verify the header zerovetor, filesystemguid,\r
+  // revision, signature, attributes, fvlength, checksum\r
+  // HeaderLength cannot be an odd number\r
+  //\r
+  NvVarStoreFvHeader = (EFI_FIRMWARE_VOLUME_HEADER *)NvVarStoreBase;\r
+\r
+  if ((!IsZeroBuffer (NvVarStoreFvHeader->ZeroVector, 16)) ||\r
+      (!CompareGuid (&FvHdrGUID, &NvVarStoreFvHeader->FileSystemGuid)) ||\r
+      (NvVarStoreFvHeader->Signature != EFI_FVH_SIGNATURE) ||\r
+      (NvVarStoreFvHeader->Attributes != 0x4feff) ||\r
+      ((NvVarStoreFvHeader->HeaderLength & 0x01) != 0) ||\r
+      (NvVarStoreFvHeader->Revision != EFI_FVH_REVISION) ||\r
+      (NvVarStoreFvHeader->FvLength != NvVarStoreSize)\r
+      )\r
+  {\r
+    DEBUG ((DEBUG_ERROR, "NvVarStore FV headers were invalid.\n"));\r
+    return FALSE;\r
+  }\r
+\r
+  //\r
+  // Verify the header checksum\r
+  //\r
+  Checksum = CalculateSum16 ((VOID *)NvVarStoreFvHeader, NvVarStoreFvHeader->HeaderLength);\r
+\r
+  if (Checksum != 0) {\r
+    DEBUG ((DEBUG_ERROR, "NvVarStore FV checksum was invalid.\n"));\r
+    return FALSE;\r
+  }\r
+\r
+  //\r
+  // Verify the header signature, size, format, state\r
+  //\r
+  NvVarStoreHeader = (VARIABLE_STORE_HEADER *)(NvVarStoreBase + NvVarStoreFvHeader->HeaderLength);\r
+  if ((!CompareGuid (&VarStoreHdrGUID, &NvVarStoreHeader->Signature)) ||\r
+      (NvVarStoreHeader->Format != VARIABLE_STORE_FORMATTED) ||\r
+      (NvVarStoreHeader->State != VARIABLE_STORE_HEALTHY) ||\r
+      (NvVarStoreHeader->Size > (NvVarStoreFvHeader->FvLength - NvVarStoreFvHeader->HeaderLength)) ||\r
+      (NvVarStoreHeader->Size < sizeof (VARIABLE_STORE_HEADER))\r
+      )\r
+  {\r
+    DEBUG ((DEBUG_ERROR, "NvVarStore header signature/size/format/state were invalid.\n"));\r
+    return FALSE;\r
+  }\r
+\r
+  //\r
+  // Verify the header startId, state\r
+  // Verify data to the end\r
+  //\r
+  VariableBase = (UINTN)NvVarStoreBase + NvVarStoreFvHeader->HeaderLength + sizeof (VARIABLE_STORE_HEADER);\r
+  while (VariableOffset  < (NvVarStoreHeader->Size - sizeof (VARIABLE_STORE_HEADER))) {\r
+    VariableHeader = (AUTHENTICATED_VARIABLE_HEADER *)(VariableBase + VariableOffset);\r
+    if (VariableHeader->StartId != VARIABLE_DATA) {\r
+      if (!CheckPaddingData ((UINT8 *)VariableHeader, NvVarStoreHeader->Size - sizeof (VARIABLE_STORE_HEADER) - VariableOffset)) {\r
+        DEBUG ((DEBUG_ERROR, "NvVarStore variable header StartId was invalid.\n"));\r
+        return FALSE;\r
+      }\r
+\r
+      VariableOffset = NvVarStoreHeader->Size - sizeof (VARIABLE_STORE_HEADER);\r
+    } else {\r
+      if (!((VariableHeader->State == VAR_HEADER_VALID_ONLY) ||\r
+            (VariableHeader->State == VAR_ADDED) ||\r
+            (VariableHeader->State == (VAR_ADDED & VAR_DELETED)) ||\r
+            (VariableHeader->State == (VAR_ADDED & VAR_IN_DELETED_TRANSITION)) ||\r
+            (VariableHeader->State == (VAR_ADDED & VAR_IN_DELETED_TRANSITION & VAR_DELETED))))\r
+      {\r
+        DEBUG ((DEBUG_ERROR, "NvVarStore Variable header State was invalid.\n"));\r
+        return FALSE;\r
+      }\r
+\r
+      VariableOffset += sizeof (AUTHENTICATED_VARIABLE_HEADER) + VariableHeader->NameSize + VariableHeader->DataSize;\r
+      // Verify VariableOffset should be less than or equal NvVarStoreHeader->Size - sizeof(VARIABLE_STORE_HEADER)\r
+      if (VariableOffset > (NvVarStoreHeader->Size - sizeof (VARIABLE_STORE_HEADER))) {\r
+        DEBUG ((DEBUG_ERROR, "NvVarStore Variable header VariableOffset was invalid.\n"));\r
+        return FALSE;\r
+      }\r
+\r
+      VariableOffsetBeforeAlign = VariableOffset;\r
+      // 4 byte align\r
+      VariableOffset = (VariableOffset  + 3) & (UINTN)(~3);\r
+\r
+      if (!CheckPaddingData ((UINT8 *)(VariableBase + VariableOffsetBeforeAlign), VariableOffset - VariableOffsetBeforeAlign)) {\r
+        DEBUG ((DEBUG_ERROR, "NvVarStore Variable header PaddingData was invalid.\n"));\r
+        return FALSE;\r
+      }\r
+    }\r
+  }\r
+\r
+  return TRUE;\r
+}\r
+\r
+/**\r
+ Allocate storage for NV variables early on so it will be\r
+ at a consistent address.  Since VM memory is preserved\r
+ across reboots, this allows the NV variable storage to survive\r
+ a VM reboot.\r
+\r
+ *\r
+ * @retval VOID* The pointer to the storage for NV Variables\r
+ */\r
+VOID *\r
+EFIAPI\r
+PlatformReserveEmuVariableNvStore (\r
+  VOID\r
+  )\r
+{\r
+  VOID    *VariableStore;\r
+  UINT32  VarStoreSize;\r
+\r
+  VarStoreSize = 2 * PcdGet32 (PcdFlashNvStorageFtwSpareSize);\r
+  //\r
+  // Allocate storage for NV variables early on so it will be\r
+  // at a consistent address.  Since VM memory is preserved\r
+  // across reboots, this allows the NV variable storage to survive\r
+  // a VM reboot.\r
+  //\r
+  VariableStore =\r
+    AllocateRuntimePages (\r
+      EFI_SIZE_TO_PAGES (VarStoreSize)\r
+      );\r
+  DEBUG ((\r
+    DEBUG_INFO,\r
+    "Reserved variable store memory: 0x%p; size: %dkb\n",\r
+    VariableStore,\r
+    VarStoreSize / 1024\r
+    ));\r
+\r
+  return VariableStore;\r
+}\r
+\r
+/**\r
+ When OVMF is lauched with -bios parameter, UEFI variables will be\r
+ partially emulated, and non-volatile variables may lose their contents\r
+ after a reboot. This makes the secure boot feature not working.\r
+\r
+ This function is used to initialize the EmuVariableNvStore\r
+ with the conent in PcdOvmfFlashNvStorageVariableBase.\r
+\r
+ @param[in] EmuVariableNvStore      - A pointer to EmuVariableNvStore\r
+\r
+ @retval  EFI_SUCCESS   - Successfully init the EmuVariableNvStore\r
+ @retval  Others        - As the error code indicates\r
+ */\r
+EFI_STATUS\r
+EFIAPI\r
+PlatformInitEmuVariableNvStore (\r
+  IN VOID  *EmuVariableNvStore\r
+  )\r
+{\r
+  UINT8   *Base;\r
+  UINT32  Size;\r
+  UINT32  EmuVariableNvStoreSize;\r
+\r
+  EmuVariableNvStoreSize = 2 * PcdGet32 (PcdFlashNvStorageFtwSpareSize);\r
+  if ((EmuVariableNvStore == NULL) || (EmuVariableNvStoreSize == 0)) {\r
+    DEBUG ((DEBUG_ERROR, "Invalid EmuVariableNvStore parameter.\n"));\r
+    return EFI_INVALID_PARAMETER;\r
+  }\r
+\r
+  Base = (UINT8 *)(UINTN)PcdGet32 (PcdOvmfFlashNvStorageVariableBase);\r
+  Size = (UINT32)PcdGet32 (PcdFlashNvStorageVariableSize);\r
+  ASSERT (Size < EmuVariableNvStoreSize);\r
+\r
+  if (!PlatformValidateNvVarStore (Base, PcdGet32 (PcdCfvRawDataSize))) {\r
+    ASSERT (FALSE);\r
+    return EFI_INVALID_PARAMETER;\r
+  }\r
+\r
+  DEBUG ((DEBUG_INFO, "Init EmuVariableNvStore with the content in FlashNvStorage\n"));\r
+\r
+  CopyMem (EmuVariableNvStore, Base, Size);\r
+\r
+  return EFI_SUCCESS;\r
+}\r