在 12/24/21 9:52 PM, Marvin Häuser 写道:
> On 24.12.21 02:18, Ming Huang wrote:
>>
>> 在 12/23/21 7:05 PM, Marvin Häuser 写道:
>>> On 23.12.21 11:46, Ming Huang wrote:
>>>> 在 12/16/21 5:15 PM, Marvin Häuser 写道:
>>>>> Hey all,
>>>>>
>>>>>> On 15. Dec 2021, at 16:02, Ming Huang <huangm...@linux.alibaba.com>
>>>>>> wrote:
>>>>>>
>>>>>>
>>>>>>
>>>>>>> On 12/9/21 1:46 AM, Omkar Anand Kulkarni wrote:
>>>>>>> Hi Ming,
>>>>>>> Thanks for this patch. This patch helps to resolve Standalone MM issue
>>>>>>> while exercising RAS use case.
>>>>>>> Few comments mentioned inline.
>>>>>>> - Omkar
>>>>>>>> On 10/15/21 2:39 PM, Ming Huang via groups.io wrote:
>>>>>>>> There are two scene communicate with StandaloneMm(MM):
>>>>>>>> 1 edk2 -> TF-A -> MM, communicate MM use non-secure buffer which
>>>>>>>> specify by EFI_SECURE_PARTITION_BOOT_INFO.SpNsCommBufBase;
>>>>>>>> 2 RAS scene: fiq -> TF-A -> MM, use secure buffer which
>>>>>>>> specify by EFI_SECURE_PARTITION_BOOT_INFO.SpShareBufBase;
>>>>>>>> For now, the second scene will failed because check buffer address.
>>>>>>>> This patch add CheckBufferAddr() to support check address for secure
>>>>>>>> buffer.
>>>>>>>> Signed-off-by: Ming Huang <huangm...@linux.alibaba.com>
>>>>>>>> ---
>>>>>>>> StandaloneMmPkg/Drivers/StandaloneMmCpu/EventHandle.c | 70
>>>>>>>> ++++++++++++++++----
>>>>>>>> StandaloneMmPkg/Drivers/StandaloneMmCpu/StandaloneMmCpu.c | 21
>>>>>>>> ++++++
>>>>>>>> StandaloneMmPkg/Drivers/StandaloneMmCpu/StandaloneMmCpu.h | 1 +
>>>>>>>> 3 files changed, 79 insertions(+), 13 deletions(-)
>>>>>>>> diff --git a/StandaloneMmPkg/Drivers/StandaloneMmCpu/EventHandle.c
>>>>>>>> b/StandaloneMmPkg/Drivers/StandaloneMmCpu/EventHandle.c
>>>>>>>> index 5dfaf9d751..63fab1bd78 100644
>>>>>>>> --- a/StandaloneMmPkg/Drivers/StandaloneMmCpu/EventHandle.c
>>>>>>>> +++ b/StandaloneMmPkg/Drivers/StandaloneMmCpu/EventHandle.c
>>>>>>>> @@ -50,6 +50,7 @@ EFI_MM_COMMUNICATE_HEADER
>>>>>>>> **PerCpuGuidedEventContext = NULL;
>>>>>>>> // Descriptor with whereabouts of memory used for communication with
>>>>>>>> the normal world EFI_MMRAM_DESCRIPTOR mNsCommBuffer;
>>>>>>>> +EFI_MMRAM_DESCRIPTOR mSCommBuffer;
>>>>>>>> MP_INFORMATION_HOB_DATA *mMpInformationHobData;
>>>>>>>> @@ -60,6 +61,58 @@ EFI_MM_CONFIGURATION_PROTOCOL mMmConfig =
>>>>>>>> {
>>>>>>>> STATIC EFI_MM_ENTRY_POINT mMmEntryPoint = NULL;
>>>>>>>> +STATIC
>>>>>>>> +EFI_STATUS
>>>>>>>> +CheckBufferAddr (
>>>>>>>> + IN UINTN CommBufferAddr
>>>>>>>> + )
>>>>>>>> +{
>>>>>>>> + UINTN CommBufferSize;
>>>>>>>> + EFI_STATUS Status;
>>>>>>>> +
>>>>>>>> + Status = EFI_SUCCESS;
>>>>>>>> + if (CommBufferAddr < mNsCommBuffer.PhysicalStart) {
>>>>>>>> + Status = EFI_ACCESS_DENIED;
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + if ((CommBufferAddr + sizeof (EFI_MM_COMMUNICATE_HEADER)) >=
>>>>>>>> + (mNsCommBuffer.PhysicalStart + mNsCommBuffer.PhysicalSize)) {
>>>>>>>> + Status = EFI_INVALID_PARAMETER;
>>>>>>> Single space after "Status = "
>>>>>> Modify it in v2.
>>>>>>
>>>>>>> - Omkar
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + // Find out the size of the buffer passed CommBufferSize =
>>>>>>>> + ((EFI_MM_COMMUNICATE_HEADER *) CommBufferAddr)->MessageLength
>>>>>>>> +
>>>>>>>> + sizeof (EFI_MM_COMMUNICATE_HEADER);
>>>>>>>> +
>>>>>>>> + // perform bounds check.
>>>>>>>> + if (CommBufferAddr + CommBufferSize >=
>>>>>>>> + mNsCommBuffer.PhysicalStart + mNsCommBuffer.PhysicalSize) {
>>>>>>>> + Status = EFI_ACCESS_DENIED;
>>>>>>> Single space after "Status = "
>>>>>> Modify it in v2.
>>>>>>
>>>>>>> - Omkar
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + if (!EFI_ERROR (Status)) {
>>>>>>> In case of error this function call will not return from here. It will
>>>>>>> execute the code below comparing the MM Communicate buffer address with
>>>>>>> the Secure buffer address, which may cause wrong return type being
>>>>>>> returned. Can you check this, please?
>>>>>>> - Omkar
>>>>>>>> + return EFI_SUCCESS;
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + Status = EFI_SUCCESS;
>>>>>>>> + if (CommBufferAddr < mSCommBuffer.PhysicalStart) {
>>>>>>>> + Status = EFI_ACCESS_DENIED;
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + if ((CommBufferAddr + sizeof (EFI_MM_COMMUNICATE_HEADER)) >=
>>>>>>>> + (mSCommBuffer.PhysicalStart + mSCommBuffer.PhysicalSize)) {
>>>>>>>> + Status = EFI_INVALID_PARAMETER;
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + // perform bounds check.
>>>>>>>> + if (CommBufferAddr + CommBufferSize >=
>>>>>>>> + mSCommBuffer.PhysicalStart + mSCommBuffer.PhysicalSize) {
>>>>>>>> + Status = EFI_ACCESS_DENIED;
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + return Status;
>>>>>>>> +}
>>>>>>>> +
>>>>>>> CheckBufferAddr() function performs validity and overflow checks on the
>>>>>>> Communication buffers. These checks are same for both the non-secure
>>>>>>> MM communicate buffer and secure buffer shared between EL3 and S-EL0.
>>>>>>> Can this code be combined ( example below)? This will help mitigate the
>>>>>>> above mentioned return type issue as well.
>>>>>> Your example is a good idea to solve this case. I may modify it like
>>>>>> below in v2:
>>>>>>
>>>>>> STATIC
>>>>>> EFI_STATUS
>>>>>> CheckBufferAddr (
>>>>>> IN UINTN CommBufferAddr
>>>>>> )
>>>>>> {
>>>>>> UINTN CommBufferSize;
>>>>>> EFI_STATUS Status;
>>>>>> UINT64 NsCommBufferEnd;
>>>>>> UINT64 SCommBufferEnd;
>>>>>> UINT64 CommBufferEnd;
>>>>>>
>>>>>> Status = EFI_SUCCESS;
>>>>>> NsCommBufferEnd = mNsCommBuffer.PhysicalStart +
>>>>>> mNsCommBuffer.PhysicalSize;
>>>>>> SCommBufferEnd = mSCommBuffer.PhysicalStart + mSCommBuffer.PhysicalSize;
>>>>>>
>>>>>> if ((CommBufferAddr >= mNsCommBuffer.PhysicalStart) &&
>>>>>> (CommBufferAddr < NsCommBufferEnd)) {
>>>>>> CommBufferEnd = NsCommBufferEnd;
>>>>>> } else if ((CommBufferAddr >= mSCommBuffer.PhysicalStart) &&
>>>>>> (CommBufferAddr <= SCommBufferEnd)) {
>>>>> I find it odd the check here (lesser-equals) is inconsistent with the
>>>>> check above (lesser). It’d be caught below anyway, but I’d change this to
>>>>> lesser to keep the return codes consistent.
>>>> Should be lesser, modify it in v3.
>>>>
>>>>>> CommBufferEnd = SCommBufferEnd;
>>>>>> } else {
>>>>>> return EFI_ACCESS_DENIED;
>>>>>> }
>>>>>>
>>>>>> if ((CommBufferAddr + sizeof (EFI_MM_COMMUNICATE_HEADER)) >=
>>>>>> CommBufferEnd) {
>>>>> Why is greater-equals used here? MessageLength == 0 is not filtered
>>>>> below, so this looks odd to be honest, as this is only the theoretical
>>>>> maximum buffer end.
>>>>>
>>>>> How do you know this cannot wraparound? I actually don’t think we do. We
>>>>> do know it holds that CommBufferAddr <= CommBufferEnd though, so checking
>>>>> CommBufferEnd - CommBufferAddr < sizeof (EFI_MM_COMMUNICATE_HEADER) would
>>>>> give you that for free, if we assume the UINT64 variables above are
>>>>> actually bounded by UINTN, which seems reasonable - could ASSERT.
>>>> In my mind, (CommBufferAddr + sizeof (EFI_MM_COMMUNICATE_HEADER)) >=
>>>> CommBufferEnd)
>>>> is the same with: CommBufferEnd - CommBufferAddr < sizeof
>>>> (EFI_MM_COMMUNICATE_HEADER)
>>> Okay, assume:
>>> CommBufferEnd = MAX_UINTN
>>> CommBufferAddr = MAX_UINTN - 1 (MAX_UINTN - 1 < MAX_UINTN, so the check
>>> above would pass)
>>> sizeof (EFI_MM_COMMUNICATE_HEADER) = 16 (should be accurate for 64-bit
>>> architectures)
>>>
>>> Then (assume implicit mod 2^N on both sides due to bounded integers!):
>>> (CommBufferAddr + sizeof (EFI_MM_COMMUNICATE_HEADER)) >= CommBufferEnd) <=>
>>> ((MAX_UINTN - 1) + 16) >= MAX_UINTN <=> MAX_UINTN + 15 >= MAX_UINTN
>>> <=>(wraparound!!) 14 >= MAX_UINTN <=> FALSE
>>>
>>> And (assume implicit mod 2^N on both sides due to bounded integers!):
>>> CommBufferEnd - CommBufferAddr < sizeof (EFI_MM_COMMUNICATE_HEADER) <=>
>>> MAX_UINTN - (MAX_UINTN - 1) < 16 <=> 1 < 16 <=> TRUE
>>>
>>> Due to wraparound semantics and the knowledge derived by the if-checks
>>> above they are by no means the same. The left term of the first equation
>>> can wrap around (or it cannot, but then we need some concrete proof that it
>>> cannot), and the left term of the second equation obviously cannot
>>> (directly follows from the if statements before).
>> I got it. Thanks for your proper comments.
>> I may modify it like below in v3:
>> ----------------------------------------
>> STATIC
>> EFI_STATUS
>> CheckBufferAddr (
>> IN UINTN BufferAddr
>> )
>> {
>> UINTN BufferSize;
>> UINT64 NsCommBufferEnd;
>> UINT64 SCommBufferEnd;
>> UINT64 CommBufferEnd;
>>
>> NsCommBufferEnd = mNsCommBuffer.PhysicalStart +
>> mNsCommBuffer.PhysicalSize;
>> SCommBufferEnd = mSCommBuffer.PhysicalStart + mSCommBuffer.PhysicalSize;
>>
>> if ((BufferAddr >= mNsCommBuffer.PhysicalStart) &&
>> (BufferAddr < NsCommBufferEnd)) {
>> CommBufferEnd = NsCommBufferEnd;
>> } else if ((BufferAddr >= mSCommBuffer.PhysicalStart) &&
>> (BufferAddr < SCommBufferEnd)) {
>> CommBufferEnd = SCommBufferEnd;
>> } else {
>> return EFI_ACCESS_DENIED;
>> }
>>
>> if ((CommBufferEnd - BufferAddr) < sizeof (EFI_MM_COMMUNICATE_HEADER)) {
>> return EFI_INVALID_PARAMETER;
>
> Just another cosmetic thing I just noticed, why is this Invalid Parameter?
> The check above yields Access Denied when the buffer start is out of a
> trusted range, OK. The check below yields Access Denied when the buffer data
> extends beyond the trusted range, OK. What makes this check different from
> the other ones that it gets a different return code? I'm not sure on the
> policy of function documentation (i.e. whether one is needed), but what would
> the difference be in their descriptions?
Okay, EFI_ACCESS_DENIED should be return. Modify it in v3.
>
>> }
>>
>> // Find out the size of the buffer passed
>> BufferSize = ((EFI_MM_COMMUNICATE_HEADER *) BufferAddr)->MessageLength +
>> sizeof (EFI_MM_COMMUNICATE_HEADER);
>
> Same issue as above, can also be solved by rewriting as subtraction. Because
> you now know that (CommBufferEnd - BufferAddr) >= sizeof
> (EFI_MM_COMMUNICATE_HEADER).
Sorry, I haven't understood what you mean. Could you rewrite CheckBufferAddr()
as a sample?
Thank you very much.
Merry Christmas!
- Ming
>
> Best regards,
> Marvin
>
>> // perform bounds check.
>> if ((CommBufferEnd - BufferAddr) < BufferSize) {
>> return EFI_ACCESS_DENIED;
>> }
>>
>> return EFI_SUCCESS;
>> }
>> ----------------------------------------
>>
>> - Ming
>>
>>>>> Alternatively, you could not store the maximum buffer end but the maximum
>>>>> buffer size, so the additions of the buffer start would just vanish. This
>>>>> might be more readable too I think.
>>>> As CommBufferAddr may be not at the begin of communicate buffer,
>>>> so check size with the maximum buffer size is not enough.
>>>>
>>>>>> Status = EFI_INVALID_PARAMETER;
>>>>> Why is there no return here? This can proceed when the buffer cannot fit
>>>>> this header, and yet below the header is dereferenced.
>>>> Modify it in v3.
>>>>
>>>>>> }
>>>>>>
>>>>>> // Find out the size of the buffer passed
>>>>>> CommBufferSize = ((EFI_MM_COMMUNICATE_HEADER *)
>>>>>> CommBufferAddr)->MessageLength +
>>>>>> sizeof (EFI_MM_COMMUNICATE_HEADER);
>>>>> Same wraparound concern, same suggestion for solving it.
>>>>>
>>>>>> // perform bounds check.
>>>>>> if (CommBufferAddr + CommBufferSize >= CommBufferEnd) {
>>>>>> Status = EFI_ACCESS_DENIED;
>>>>> It’s obviously not bad here, but for consistency’s sake, to mitigate bugs
>>>>> introduced by future changes, and readability, I’d return here and just
>>>>> return EFI_SUCCESS below, removing the code requirement of keeping Status
>>>>> consistent with the check results.
>>>> Modify it in v3.
>>> Thanks for the modifications.
>>>
>>> Best regards,
>>> Marvin
>>>
>>>> - Ming
>>>>
>>>>> Finally, I really believe this kind of function should be abstracted in a
>>>>> way that it can be consumed by all places that accept any sort of
>>>>> communication buffer. Buffer validity checking is too critical than to
>>>>> duplicate it in every consumer.
>>>>>
>>>>> Thanks!
>>>>>
>>>>> Best regards,
>>>>> Marvin
>>>>>
>>>>>> }
>>>>>>
>>>>>> return Status;
>>>>>> }
>>>>>>
>>>>>> - Ming
>>>>>>
>>>>>>> STATIC
>>>>>>> EFI_STATUS
>>>>>>> CheckBufferAddr (
>>>>>>> IN UINTN CommBufferAddr
>>>>>>> )
>>>>>>> {
>>>>>>> UINTN CommBufferSize;
>>>>>>> EFI_STATUS Status;
>>>>>>> EFI_MMRAM_DESCRIPTOR CommBuffer;
>>>>>>> if (CommBufferAddr < mNsCommBuffer.PhysicalStart ||
>>>>>>> CommBufferAddr > (mNsCommBuffer.PhysicalStart +
>>>>>>> mNsCommBuffer.PhysicalSize)) {
>>>>>>> CommBuffer = mSCommBuffer;
>>>>>>> } else {
>>>>>>> CommBuffer = mNsCommBuffer;
>>>>>>> }
>>>>>>> if (CommBufferAddr < CommBuffer.PhysicalStart) {
>>>>>>> Status = EFI_ACCESS_DENIED;
>>>>>>> }
>>>>>>> if ((CommBufferAddr + sizeof (EFI_MM_COMMUNICATE_HEADER)) >=
>>>>>>> (CommBuffer.PhysicalStart + CommBuffer.PhysicalSize)) {
>>>>>>> Status = EFI_INVALID_PARAMETER;
>>>>>>> }
>>>>>>> // Find out the size of the buffer passed
>>>>>>> CommBufferSize = ((EFI_MM_COMMUNICATE_HEADER *)
>>>>>>> CommBufferAddr)->MessageLength +
>>>>>>> sizeof (EFI_MM_COMMUNICATE_HEADER);
>>>>>>> // perform bounds check.
>>>>>>> if (CommBufferAddr + CommBufferSize >=
>>>>>>> CommBuffer.PhysicalStart + CommBuffer.PhysicalSize) {
>>>>>>> Status = EFI_ACCESS_DENIED;
>>>>>>> }
>>>>>>> return Status;
>>>>>>> }
>>>>>>> - Omkar
>>>>>>>> /**
>>>>>>>> The PI Standalone MM entry point for the TF-A CPU driver.
>>>>>>>> @@ -104,25 +157,16 @@ PiMmStandaloneArmTfCpuDriverEntry (
>>>>>>>> return EFI_INVALID_PARAMETER;
>>>>>>>> }
>>>>>>>> - if (NsCommBufferAddr < mNsCommBuffer.PhysicalStart) {
>>>>>>>> - return EFI_ACCESS_DENIED;
>>>>>>>> - }
>>>>>>>> -
>>>>>>>> - if ((NsCommBufferAddr + sizeof (EFI_MM_COMMUNICATE_HEADER)) >=
>>>>>>>> - (mNsCommBuffer.PhysicalStart + mNsCommBuffer.PhysicalSize)) {
>>>>>>>> - return EFI_INVALID_PARAMETER;
>>>>>>>> + Status = CheckBufferAddr (NsCommBufferAddr); if (EFI_ERROR
>>>>>>>> (Status))
>>>>>>>> + {
>>>>>>>> + DEBUG ((DEBUG_ERROR, "Check Buffer failed: %r\n", Status));
>>>>>>>> + return Status;
>>>>>>>> }
>>>>>>>> // Find out the size of the buffer passed
>>>>>>>> NsCommBufferSize = ((EFI_MM_COMMUNICATE_HEADER *)
>>>>>>>> NsCommBufferAddr)->MessageLength +
>>>>>>>> sizeof (EFI_MM_COMMUNICATE_HEADER);
>>>>>>>> - // perform bounds check.
>>>>>>>> - if (NsCommBufferAddr + NsCommBufferSize >=
>>>>>>>> - mNsCommBuffer.PhysicalStart + mNsCommBuffer.PhysicalSize) {
>>>>>>>> - return EFI_ACCESS_DENIED;
>>>>>>>> - }
>>>>>>>> -
>>>>>>>> GuidedEventContext = NULL;
>>>>>>>> // Now that the secure world can see the normal world buffer, allocate
>>>>>>>> // memory to copy the communication buffer to the secure world.
>>>>>>>> diff --git
>>>>>>>> a/StandaloneMmPkg/Drivers/StandaloneMmCpu/StandaloneMmCpu.c
>>>>>>>> b/StandaloneMmPkg/Drivers/StandaloneMmCpu/StandaloneMmCpu.c
>>>>>>>> index fd9c59b4da..96dad20dd1 100644
>>>>>>>> --- a/StandaloneMmPkg/Drivers/StandaloneMmCpu/StandaloneMmCpu.c
>>>>>>>> +++ b/StandaloneMmPkg/Drivers/StandaloneMmCpu/StandaloneMmCpu.c
>>>>>>>> @@ -107,6 +107,7 @@ StandaloneMmCpuInitialize (
>>>>>>>> UINTN Index;
>>>>>>>> UINTN ArraySize;
>>>>>>>> VOID *HobStart;
>>>>>>>> + EFI_MMRAM_HOB_DESCRIPTOR_BLOCK *MmramRangesHob;
>>>>>>>> ASSERT (SystemTable != NULL);
>>>>>>>> mMmst = SystemTable;
>>>>>>>> @@ -186,6 +187,26 @@ StandaloneMmCpuInitialize (
>>>>>>>> CopyMem (&mNsCommBuffer, NsCommBufMmramRange,
>>>>>>>> sizeof(EFI_MMRAM_DESCRIPTOR));
>>>>>>>> DEBUG ((DEBUG_INFO, "mNsCommBuffer: 0x%016lx - 0x%lx\n",
>>>>>>>> mNsCommBuffer.CpuStart, mNsCommBuffer.PhysicalSize));
>>>>>>>> + Status = GetGuidedHobData (
>>>>>>>> + HobStart,
>>>>>>>> + &gEfiMmPeiMmramMemoryReserveGuid,
>>>>>>>> + (VOID **) &MmramRangesHob
>>>>>>>> + );
>>>>>>>> + if (EFI_ERROR (Status)) {
>>>>>>>> + DEBUG ((DEBUG_ERROR, "MmramRangesHob data extraction failed -
>>>>>>>> 0x%x\n", Status));
>>>>>>>> + return Status;
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + //
>>>>>>>> + // As CreateHobListFromBootInfo(), the base and size of buffer
>>>>>>>> shared
>>>>>>>> + with // privileged Secure world software is in second one.
>>>>>>>> + //
>>>>>>>> + CopyMem (
>>>>>>>> + &mSCommBuffer,
>>>>>>>> + &MmramRangesHob->Descriptor[0] + 1,
>>>>>>> Can this be changed to
>>>>>>> &MmramRangesHob->Descriptor[1],
>>>>>>> - Omkar
>>>>>>>> + sizeof(EFI_MMRAM_DESCRIPTOR)
>>>>>>>> + );
>>>>>>>> +
>>>>>>>> //
>>>>>>>> // Extract the MP information from the Hoblist
>>>>>>>> //
>>>>>>>> diff --git
>>>>>>>> a/StandaloneMmPkg/Drivers/StandaloneMmCpu/StandaloneMmCpu.h
>>>>>>>> b/StandaloneMmPkg/Drivers/StandaloneMmCpu/StandaloneMmCpu.h
>>>>>>>> index 2c96439c15..2e03b20d85 100644
>>>>>>>> --- a/StandaloneMmPkg/Drivers/StandaloneMmCpu/StandaloneMmCpu.h
>>>>>>>> +++ b/StandaloneMmPkg/Drivers/StandaloneMmCpu/StandaloneMmCpu.h
>>>>>>>> @@ -30,6 +30,7 @@ extern EFI_MM_CPU_PROTOCOL mMmCpuState; //
>>>>>>>> extern EFI_MM_COMMUNICATE_HEADER **PerCpuGuidedEventContext;
>>>>>>>> extern EFI_MMRAM_DESCRIPTOR mNsCommBuffer;
>>>>>>>> +extern EFI_MMRAM_DESCRIPTOR mSCommBuffer;
>>>>>>>> extern MP_INFORMATION_HOB_DATA *mMpInformationHobData;
>>>>>>>> extern EFI_MM_CONFIGURATION_PROTOCOL mMmConfig;
>>>>>>>> --
>>>>>>>> 2.17.1
>>>>>>> IMPORTANT NOTICE: The contents of this email and any attachments are
>>>>>>> confidential and may also be privileged. If you are not the intended
>>>>>>> recipient, please notify the sender immediately and do not disclose the
>>>>>>> contents to any other person, use it for any purpose, or store or copy
>>>>>>> the information in any medium. Thank you.
>>>>>>
-=-=-=-=-=-=-=-=-=-=-=-
Groups.io Links: You receive all messages sent to this group.
View/Reply Online (#85214): https://edk2.groups.io/g/devel/message/85214
Mute This Topic: https://groups.io/mt/86334815/21656
Group Owner: devel+ow...@edk2.groups.io
Unsubscribe: https://edk2.groups.io/g/devel/unsub [arch...@mail-archive.com]
-=-=-=-=-=-=-=-=-=-=-=-