From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mga03.intel.com (mga03.intel.com [134.134.136.65]) by mx.groups.io with SMTP id smtpd.web10.2729.1577755049189654053 for ; Mon, 30 Dec 2019 17:17:29 -0800 Authentication-Results: mx.groups.io; dkim=missing; spf=pass (domain: intel.com, ip: 134.134.136.65, mailfrom: eric.dong@intel.com) X-Amp-Result: SKIPPED(no attachment in message) X-Amp-File-Uploaded: False Received: from fmsmga002.fm.intel.com ([10.253.24.26]) by orsmga103.jf.intel.com with ESMTP/TLS/DHE-RSA-AES256-GCM-SHA384; 30 Dec 2019 17:17:28 -0800 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.69,377,1571727600"; d="scan'208";a="251542375" Received: from fmsmsx107.amr.corp.intel.com ([10.18.124.205]) by fmsmga002.fm.intel.com with ESMTP; 30 Dec 2019 17:17:28 -0800 Received: from fmsmsx115.amr.corp.intel.com (10.18.116.19) by fmsmsx107.amr.corp.intel.com (10.18.124.205) with Microsoft SMTP Server (TLS) id 14.3.439.0; Mon, 30 Dec 2019 17:17:28 -0800 Received: from shsmsx106.ccr.corp.intel.com (10.239.4.159) by fmsmsx115.amr.corp.intel.com (10.18.116.19) with Microsoft SMTP Server (TLS) id 14.3.439.0; Mon, 30 Dec 2019 17:17:27 -0800 Received: from shsmsx102.ccr.corp.intel.com ([169.254.2.202]) by SHSMSX106.ccr.corp.intel.com ([169.254.10.139]) with mapi id 14.03.0439.000; Tue, 31 Dec 2019 09:17:25 +0800 From: "Dong, Eric" To: "Wu, Hao A" , "devel@edk2.groups.io" CC: "Ni, Ray" , Laszlo Ersek , "Zeng, Star" , "Fu, Siyuan" , "Kinney, Michael D" Subject: Re: [PATCH v5 2/6] UefiCpuPkg/MpInitLib: Reduce the size when loading microcode patches Thread-Topic: [PATCH v5 2/6] UefiCpuPkg/MpInitLib: Reduce the size when loading microcode patches Thread-Index: AQHVv3QomXSCa8jIQkysNC4DNKlAfafTcTAQ Date: Tue, 31 Dec 2019 01:17:25 +0000 Message-ID: References: <20191231004914.8520-1-hao.a.wu@intel.com> <20191231004914.8520-3-hao.a.wu@intel.com> In-Reply-To: <20191231004914.8520-3-hao.a.wu@intel.com> Accept-Language: en-US X-MS-Has-Attach: X-MS-TNEF-Correlator: x-originating-ip: [10.239.127.40] MIME-Version: 1.0 Return-Path: eric.dong@intel.com Content-Language: en-US Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: quoted-printable Reviewed-by: Eric Dong > -----Original Message----- > From: Wu, Hao A > Sent: Tuesday, December 31, 2019 8:49 AM > To: devel@edk2.groups.io > Cc: Wu, Hao A ; Dong, Eric ; Ni, > Ray ; Laszlo Ersek ; Zeng, Star > ; Fu, Siyuan ; Kinney, Michael > D > Subject: [PATCH v5 2/6] UefiCpuPkg/MpInitLib: Reduce the size when > loading microcode patches >=20 > REF:https://bugzilla.tianocore.org/show_bug.cgi?id=3D2429 >=20 > This commit will attempt to reduce the copy size when loading the microco= de > patches data from flash into memory. >=20 > Such optimization is done by a pre-process of the microcode patch headers > (on flash). A microcode patch will be loaded into memory only when the > below 3 criteria are met: >=20 > A. With a microcode patch header (which means the data is not padding dat= a > between microcode patches); > B. The 'ProcessorSignature' & 'ProcessorFlags' fields in the header match > at least one processor within system; C. If the Extended Signature Tab= le > exists in a microcode patch, the > 'ProcessorSignature' & 'ProcessorFlag' fields in the table entries > match at least one processor within system. >=20 > Criterion B and C will require all the processors to be woken up once to > collect their CPUID and Platform ID information. Hence, this commit will > move the copy, detect and apply of microcode patch on BSP and APs after a= ll > the processors have been woken up. >=20 > Cc: Eric Dong > Cc: Ray Ni > Cc: Laszlo Ersek > Cc: Star Zeng > Cc: Siyuan Fu > Cc: Michael D Kinney > Signed-off-by: Hao A Wu > --- > UefiCpuPkg/Library/MpInitLib/MpLib.h | 24 ++ > UefiCpuPkg/Library/MpInitLib/Microcode.c | 288 ++++++++++++++++++++ > UefiCpuPkg/Library/MpInitLib/MpLib.c | 90 ++---- > 3 files changed, 340 insertions(+), 62 deletions(-) >=20 > diff --git a/UefiCpuPkg/Library/MpInitLib/MpLib.h > b/UefiCpuPkg/Library/MpInitLib/MpLib.h > index 4440dc2701..56b0df664a 100644 > --- a/UefiCpuPkg/Library/MpInitLib/MpLib.h > +++ b/UefiCpuPkg/Library/MpInitLib/MpLib.h > @@ -44,6 +44,20 @@ > #define CPU_SWITCH_STATE_LOADED 2 >=20 > // > +// Default maximum number of entries to store the microcode patches > +information // #define DEFAULT_MAX_MICROCODE_PATCH_NUM 8 > + > +// > +// Data structure for microcode patch information // typedef struct { > + UINTN Address; > + UINTN Size; > + UINTN AlignedSize; > +} MICROCODE_PATCH_INFO; > + > +// > // CPU exchange information for switch BSP // typedef struct { @@ -576= ,6 > +590,16 @@ MicrocodeDetect ( > ); >=20 > /** > + Load the required microcode patches data into memory. > + > + @param[in, out] CpuMpData The pointer to CPU MP Data structure. > +**/ > +VOID > +LoadMicrocodePatch ( > + IN OUT CPU_MP_DATA *CpuMpData > + ); > + > +/** > Detect whether Mwait-monitor feature is supported. >=20 > @retval TRUE Mwait-monitor feature is supported. > diff --git a/UefiCpuPkg/Library/MpInitLib/Microcode.c > b/UefiCpuPkg/Library/MpInitLib/Microcode.c > index 199b1f23ce..330fd99623 100644 > --- a/UefiCpuPkg/Library/MpInitLib/Microcode.c > +++ b/UefiCpuPkg/Library/MpInitLib/Microcode.c > @@ -331,3 +331,291 @@ Done: > MicroData [0x%08x], Revision [0x%08x]\n", Eax.Uint32, ProcessorFl= ags, > (UINTN) MicrocodeData, LatestRevision)); > } > } > + > +/** > + Determine if a microcode patch will be loaded into memory. > + > + @param[in] CpuMpData The pointer to CPU MP Data structure= . > + @param[in] ProcessorSignature The processor signature field value > + supported by a microcode patch. > + @param[in] ProcessorFlags The prcessor flags field value suppo= rted by > + a microcode patch. > + > + @retval TRUE The specified microcode patch will be loaded. > + @retval FALSE The specified microcode patch will not be loaded. > +**/ > +BOOLEAN > +IsMicrocodePatchNeedLoad ( > + IN CPU_MP_DATA *CpuMpData, > + IN UINT32 ProcessorSignature, > + IN UINT32 ProcessorFlags > + ) > +{ > + UINTN Index; > + CPU_AP_DATA *CpuData; > + > + for (Index =3D 0; Index < CpuMpData->CpuCount; Index++) { > + CpuData =3D &CpuMpData->CpuData[Index]; > + if ((ProcessorSignature =3D=3D CpuData->ProcessorSignature) && > + (ProcessorFlags & (1 << CpuData->PlatformId)) !=3D 0) { > + return TRUE; > + } > + } > + > + return FALSE; > +} > + > +/** > + Actual worker function that loads the required microcode patches into > memory. > + > + @param[in, out] CpuMpData The pointer to CPU MP Data structure= . > + @param[in] Patches The pointer to an array of informati= on on > + the microcode patches that will be l= oaded > + into memory. > + @param[in] PatchCount The number of microcode patches that= will > + be loaded into memory. > + @param[in] TotalLoadSize The total size of all the microcode = patches > + to be loaded. > +**/ > +VOID > +LoadMicrocodePatchWorker ( > + IN OUT CPU_MP_DATA *CpuMpData, > + IN MICROCODE_PATCH_INFO *Patches, > + IN UINTN PatchCount, > + IN UINTN TotalLoadSize > + ) > +{ > + UINTN Index; > + VOID *MicrocodePatchInRam; > + UINT8 *Walker; > + > + ASSERT ((Patches !=3D NULL) && (PatchCount !=3D 0)); > + > + MicrocodePatchInRam =3D AllocatePages (EFI_SIZE_TO_PAGES > + (TotalLoadSize)); if (MicrocodePatchInRam =3D=3D NULL) { > + return; > + } > + > + // > + // Load all the required microcode patches into memory // for > + (Walker =3D MicrocodePatchInRam, Index =3D 0; Index < PatchCount; Index= ++) > { > + CopyMem ( > + Walker, > + (VOID *) Patches[Index].Address, > + Patches[Index].Size > + ); > + > + // > + // Zero-fill the padding area > + // Please note that AlignedSize will be no less than Size > + // > + ZeroMem ( > + Walker + Patches[Index].Size, > + Patches[Index].AlignedSize - Patches[Index].Size > + ); > + > + Walker +=3D Patches[Index].AlignedSize; } > + > + // > + // Update the microcode patch related fields in CpuMpData // > + CpuMpData->MicrocodePatchAddress =3D (UINTN) MicrocodePatchInRam; > + CpuMpData->MicrocodePatchRegionSize =3D TotalLoadSize; > + > + DEBUG (( > + DEBUG_INFO, > + "%a: Required microcode patches have been loaded at 0x%lx, with size > 0x%lx.\n", > + __FUNCTION__, CpuMpData->MicrocodePatchAddress, CpuMpData- > >MicrocodePatchRegionSize > + )); > + > + return; > +} > + > +/** > + Load the required microcode patches data into memory. > + > + @param[in, out] CpuMpData The pointer to CPU MP Data structure. > +**/ > +VOID > +LoadMicrocodePatch ( > + IN OUT CPU_MP_DATA *CpuMpData > + ) > +{ > + CPU_MICROCODE_HEADER *MicrocodeEntryPoint; > + UINTN MicrocodeEnd; > + UINTN DataSize; > + UINTN TotalSize; > + CPU_MICROCODE_EXTENDED_TABLE_HEADER *ExtendedTableHeader; > + UINT32 ExtendedTableCount; > + CPU_MICROCODE_EXTENDED_TABLE *ExtendedTable; > + MICROCODE_PATCH_INFO *PatchInfoBuffer; > + UINTN MaxPatchNumber; > + UINTN PatchCount; > + UINTN TotalLoadSize; > + UINTN Index; > + BOOLEAN NeedLoad; > + > + // > + // Initialize the microcode patch related fields in CpuMpData as the > + values // specified by the PCD pair. If the microcode patches are > + loaded into memory, // these fields will be updated. > + // > + CpuMpData->MicrocodePatchAddress =3D PcdGet64 > (PcdCpuMicrocodePatchAddress); > + CpuMpData->MicrocodePatchRegionSize =3D PcdGet64 > + (PcdCpuMicrocodePatchRegionSize); > + > + MicrocodeEntryPoint =3D (CPU_MICROCODE_HEADER *) (UINTN) > CpuMpData->MicrocodePatchAddress; > + MicrocodeEnd =3D (UINTN) MicrocodeEntryPoint + > + (UINTN) CpuMpData->MicrocodePatchRegionSize; > + if ((MicrocodeEntryPoint =3D=3D NULL) || ((UINTN) MicrocodeEntryPoint = =3D=3D > MicrocodeEnd)) { > + // > + // There is no microcode patches > + // > + return; > + } > + > + PatchCount =3D 0; > + MaxPatchNumber =3D DEFAULT_MAX_MICROCODE_PATCH_NUM; > + TotalLoadSize =3D 0; > + PatchInfoBuffer =3D AllocatePool (MaxPatchNumber * sizeof > + (MICROCODE_PATCH_INFO)); if (PatchInfoBuffer =3D=3D NULL) { > + return; > + } > + > + // > + // Process the header of each microcode patch within the region. > + // The purpose is to decide which microcode patch(es) will be loaded i= nto > memory. > + // > + do { > + if (MicrocodeEntryPoint->HeaderVersion !=3D 0x1) { > + // > + // Padding data between the microcode patches, skip 1KB to check n= ext > entry. > + // > + MicrocodeEntryPoint =3D (CPU_MICROCODE_HEADER *) (((UINTN) > MicrocodeEntryPoint) + SIZE_1KB); > + continue; > + } > + > + DataSize =3D MicrocodeEntryPoint->DataSize; > + TotalSize =3D (DataSize =3D=3D 0) ? 2048 : MicrocodeEntryPoint->Tota= lSize; > + if ( (UINTN)MicrocodeEntryPoint > (MAX_ADDRESS - TotalSize) || > + ((UINTN)MicrocodeEntryPoint + TotalSize) > MicrocodeEnd || > + (DataSize & 0x3) !=3D 0 || > + (TotalSize & (SIZE_1KB - 1)) !=3D 0 || > + TotalSize < DataSize > + ) { > + // > + // Not a valid microcode header, skip 1KB to check next entry. > + // > + MicrocodeEntryPoint =3D (CPU_MICROCODE_HEADER *) (((UINTN) > MicrocodeEntryPoint) + SIZE_1KB); > + continue; > + } > + > + // > + // Check the 'ProcessorSignature' and 'ProcessorFlags' of the microc= ode > + // patch header with the CPUID and PlatformID of the processors with= in > + // system to decide if it will be copied into memory > + // > + NeedLoad =3D IsMicrocodePatchNeedLoad ( > + CpuMpData, > + MicrocodeEntryPoint->ProcessorSignature.Uint32, > + MicrocodeEntryPoint->ProcessorFlags > + ); > + > + // > + // If the Extended Signature Table exists, check if the processor is= in the > + // support list > + // > + if ((!NeedLoad) && (DataSize !=3D 0) && > + (TotalSize - DataSize > sizeof (CPU_MICROCODE_HEADER) + > + sizeof (CPU_MICROCODE_EXTENDED_TABLE_HEA= DER))) { > + ExtendedTableHeader =3D (CPU_MICROCODE_EXTENDED_TABLE_HEADER > *) ((UINT8 *) (MicrocodeEntryPoint) > + + DataSize + sizeof (CPU_MICROCODE_HEADER)= ); > + ExtendedTableCount =3D ExtendedTableHeader- > >ExtendedSignatureCount; > + ExtendedTable =3D (CPU_MICROCODE_EXTENDED_TABLE *) > (ExtendedTableHeader + 1); > + > + for (Index =3D 0; Index < ExtendedTableCount; Index ++) { > + // > + // Avoid access content beyond MicrocodeEnd > + // > + if ((UINTN) ExtendedTable > MicrocodeEnd - sizeof > (CPU_MICROCODE_EXTENDED_TABLE)) { > + break; > + } > + > + // > + // Check the 'ProcessorSignature' and 'ProcessorFlag' of the Ext= ended > + // Signature Table entry with the CPUID and PlatformID of the > processors > + // within system to decide if it will be copied into memory > + // > + NeedLoad =3D IsMicrocodePatchNeedLoad ( > + CpuMpData, > + ExtendedTable->ProcessorSignature.Uint32, > + ExtendedTable->ProcessorFlag > + ); > + if (NeedLoad) { > + break; > + } > + ExtendedTable ++; > + } > + } > + > + if (NeedLoad) { > + PatchCount++; > + if (PatchCount > MaxPatchNumber) { > + // > + // Current 'PatchInfoBuffer' cannot hold the information, double= the > size > + // and allocate a new buffer. > + // > + if (MaxPatchNumber > MAX_UINTN / 2 / sizeof > (MICROCODE_PATCH_INFO)) { > + // > + // Overflow check for MaxPatchNumber > + // > + goto OnExit; > + } > + > + PatchInfoBuffer =3D ReallocatePool ( > + MaxPatchNumber * sizeof (MICROCODE_PATCH_INF= O), > + 2 * MaxPatchNumber * sizeof (MICROCODE_PATCH= _INFO), > + PatchInfoBuffer > + ); > + if (PatchInfoBuffer =3D=3D NULL) { > + goto OnExit; > + } > + MaxPatchNumber =3D MaxPatchNumber * 2; > + } > + > + // > + // Store the information of this microcode patch > + // > + if (TotalSize > ALIGN_VALUE (TotalSize, SIZE_1KB) || > + ALIGN_VALUE (TotalSize, SIZE_1KB) > MAX_UINTN - TotalLoadSize)= { > + goto OnExit; > + } > + PatchInfoBuffer[PatchCount - 1].Address =3D (UINTN) > MicrocodeEntryPoint; > + PatchInfoBuffer[PatchCount - 1].Size =3D TotalSize; > + PatchInfoBuffer[PatchCount - 1].AlignedSize =3D ALIGN_VALUE (Total= Size, > SIZE_1KB); > + TotalLoadSize +=3D PatchInfoBuffer[PatchCount - 1].AlignedSize; > + } > + > + // > + // Process the next microcode patch > + // > + MicrocodeEntryPoint =3D (CPU_MICROCODE_HEADER *) (((UINTN) > + MicrocodeEntryPoint) + TotalSize); } while (((UINTN) > + MicrocodeEntryPoint < MicrocodeEnd)); > + > + if (PatchCount !=3D 0) { > + DEBUG (( > + DEBUG_INFO, > + "%a: 0x%x microcode patches will be loaded into memory, with size > 0x%x.\n", > + __FUNCTION__, PatchCount, TotalLoadSize > + )); > + > + LoadMicrocodePatchWorker (CpuMpData, PatchInfoBuffer, PatchCount, > + TotalLoadSize); } > + > +OnExit: > + if (PatchInfoBuffer !=3D NULL) { > + FreePool (PatchInfoBuffer); > + } > + return; > +} > diff --git a/UefiCpuPkg/Library/MpInitLib/MpLib.c > b/UefiCpuPkg/Library/MpInitLib/MpLib.c > index d5077e080e..c72bf3c9ee 100644 > --- a/UefiCpuPkg/Library/MpInitLib/MpLib.c > +++ b/UefiCpuPkg/Library/MpInitLib/MpLib.c > @@ -628,10 +628,6 @@ ApWakeupFunction ( > ApTopOfStack =3D CpuMpData->Buffer + (ProcessorNumber + 1) * > CpuMpData->CpuApStackSize; > BistData =3D *(UINT32 *) ((UINTN) ApTopOfStack - sizeof (UINTN)); > // > - // Do some AP initialize sync > - // > - ApInitializeSync (CpuMpData); > - // > // CpuMpData->CpuData[0].VolatileRegisters is initialized based on= BSP > environment, > // to initialize AP in InitConfig path. > // NOTE: IDTR.BASE stored in CpuMpData->CpuData[0].VolatileRegiste= rs > points to a different IDT shared by all APs. > @@ -1615,7 +1611,6 @@ MpInitLibInitialize ( > UINTN ApResetVectorSize; > UINTN BackupBufferAddr; > UINTN ApIdtBase; > - VOID *MicrocodePatchInRam; >=20 > OldCpuMpData =3D GetCpuMpDataFromGuidedHob (); > if (OldCpuMpData =3D=3D NULL) { > @@ -1683,39 +1678,7 @@ MpInitLibInitialize ( > CpuMpData->SwitchBspFlag =3D FALSE; > CpuMpData->CpuData =3D (CPU_AP_DATA *) (CpuMpData + 1); > CpuMpData->CpuInfoInHob =3D (UINT64) (UINTN) (CpuMpData->CpuData > + MaxLogicalProcessorNumber); > - if (OldCpuMpData =3D=3D NULL) { > - CpuMpData->MicrocodePatchRegionSize =3D PcdGet64 > (PcdCpuMicrocodePatchRegionSize); > - // > - // If platform has more than one CPU, relocate microcode to memory t= o > reduce > - // loading microcode time. > - // > - MicrocodePatchInRam =3D NULL; > - if (MaxLogicalProcessorNumber > 1) { > - MicrocodePatchInRam =3D AllocatePages ( > - EFI_SIZE_TO_PAGES ( > - (UINTN)CpuMpData->MicrocodePatchRegionSi= ze > - ) > - ); > - } > - if (MicrocodePatchInRam =3D=3D NULL) { > - // > - // there is only one processor, or no microcode patch is available= , or > - // memory allocation failed > - // > - CpuMpData->MicrocodePatchAddress =3D PcdGet64 > (PcdCpuMicrocodePatchAddress); > - } else { > - // > - // there are multiple processors, and a microcode patch is availab= le, and > - // memory allocation succeeded > - // > - CopyMem ( > - MicrocodePatchInRam, > - (VOID *)(UINTN)PcdGet64 (PcdCpuMicrocodePatchAddress), > - (UINTN)CpuMpData->MicrocodePatchRegionSize > - ); > - CpuMpData->MicrocodePatchAddress =3D (UINTN)MicrocodePatchInRam; > - } > - }else { > + if (OldCpuMpData !=3D NULL) { > CpuMpData->MicrocodePatchRegionSize =3D OldCpuMpData- > >MicrocodePatchRegionSize; > CpuMpData->MicrocodePatchAddress =3D OldCpuMpData- > >MicrocodePatchAddress; > } > @@ -1762,14 +1725,6 @@ MpInitLibInitialize ( > (UINT32 *)(MonitorBuffer + MonitorFilterSize * Index); > } > // > - // Load Microcode on BSP > - // > - MicrocodeDetect (CpuMpData, TRUE); > - // > - // Store BSP's MTRR setting > - // > - MtrrGetAllMtrrs (&CpuMpData->MtrrTable); > - // > // Enable the local APIC for Virtual Wire Mode. > // > ProgramVirtualWireMode (); > @@ -1781,6 +1736,11 @@ MpInitLibInitialize ( > // > CollectProcessorCount (CpuMpData); > } > + > + // > + // Load required microcode patches data into memory > + // > + LoadMicrocodePatch (CpuMpData); > } else { > // > // APs have been wakeup before, just get the CPU Information @@ - > 1788,7 +1748,6 @@ MpInitLibInitialize ( > // > CpuMpData->CpuCount =3D OldCpuMpData->CpuCount; > CpuMpData->BspNumber =3D OldCpuMpData->BspNumber; > - CpuMpData->InitFlag =3D ApInitReconfig; > CpuMpData->CpuInfoInHob =3D OldCpuMpData->CpuInfoInHob; > CpuInfoInHob =3D (CPU_INFO_IN_HOB *) (UINTN) CpuMpData- > >CpuInfoInHob; > for (Index =3D 0; Index < CpuMpData->CpuCount; Index++) { @@ -1797,2= 1 > +1756,28 @@ MpInitLibInitialize ( > CpuMpData->CpuData[Index].ApFunction =3D 0; > CopyMem (&CpuMpData->CpuData[Index].VolatileRegisters, > &VolatileRegisters, sizeof (CPU_VOLATILE_REGISTERS)); > } > - if (MaxLogicalProcessorNumber > 1) { > - // > - // Wakeup APs to do some AP initialize sync > - // > - WakeUpAP (CpuMpData, TRUE, 0, ApInitializeSync, CpuMpData, TRUE); > - // > - // Wait for all APs finished initialization > - // > - while (CpuMpData->FinishedCount < (CpuMpData->CpuCount - 1)) { > - CpuPause (); > - } > - CpuMpData->InitFlag =3D ApInitDone; > - for (Index =3D 0; Index < CpuMpData->CpuCount; Index++) { > - SetApState (&CpuMpData->CpuData[Index], CpuStateIdle); > - } > + } > + > + // > + // Detect and apply Microcode on BSP > + // > + MicrocodeDetect (CpuMpData, TRUE); > + // > + // Store BSP's MTRR setting > + // > + MtrrGetAllMtrrs (&CpuMpData->MtrrTable); > + > + // > + // Wakeup APs to do some AP initialize sync (Microcode & MTRR) // > + if (CpuMpData->CpuCount > 1) { > + WakeUpAP (CpuMpData, TRUE, 0, ApInitializeSync, CpuMpData, TRUE); > + while (CpuMpData->FinishedCount < (CpuMpData->CpuCount - 1)) { > + CpuPause (); > + } > + > + for (Index =3D 0; Index < CpuMpData->CpuCount; Index++) { > + SetApState (&CpuMpData->CpuData[Index], CpuStateIdle); > } > } >=20 > -- > 2.12.0.windows.1