]> git.proxmox.com Git - mirror_edk2.git/blob - EmbeddedPkg/Library/NonCoherentDmaLib/NonCoherentDmaLib.c
ArmPkg/PlatformBootManagerLib: regenerate boot options on boot failure
[mirror_edk2.git] / EmbeddedPkg / Library / NonCoherentDmaLib / NonCoherentDmaLib.c
1 /** @file
2
3 Generic non-coherent implementation of DmaLib.h
4
5 Copyright (c) 2008 - 2010, Apple Inc. All rights reserved.<BR>
6 Copyright (c) 2015 - 2017, Linaro, Ltd. All rights reserved.<BR>
7
8 SPDX-License-Identifier: BSD-2-Clause-Patent
9
10 **/
11
12 #include <PiDxe.h>
13 #include <Library/BaseLib.h>
14 #include <Library/DebugLib.h>
15 #include <Library/DmaLib.h>
16 #include <Library/DxeServicesTableLib.h>
17 #include <Library/MemoryAllocationLib.h>
18 #include <Library/UefiBootServicesTableLib.h>
19 #include <Library/IoLib.h>
20 #include <Library/BaseMemoryLib.h>
21
22 #include <Protocol/Cpu.h>
23
24 typedef struct {
25 EFI_PHYSICAL_ADDRESS HostAddress;
26 VOID *BufferAddress;
27 UINTN NumberOfBytes;
28 DMA_MAP_OPERATION Operation;
29 BOOLEAN DoubleBuffer;
30 } MAP_INFO_INSTANCE;
31
32
33 typedef struct {
34 LIST_ENTRY Link;
35 VOID *HostAddress;
36 UINTN NumPages;
37 UINT64 Attributes;
38 } UNCACHED_ALLOCATION;
39
40 STATIC EFI_CPU_ARCH_PROTOCOL *mCpu;
41 STATIC LIST_ENTRY UncachedAllocationList;
42
43 STATIC PHYSICAL_ADDRESS mDmaHostAddressLimit;
44
45 STATIC
46 PHYSICAL_ADDRESS
47 HostToDeviceAddress (
48 IN VOID *Address
49 )
50 {
51 return (PHYSICAL_ADDRESS)(UINTN)Address + PcdGet64 (PcdDmaDeviceOffset);
52 }
53
54 /**
55 Allocates one or more 4KB pages of a certain memory type at a specified
56 alignment.
57
58 Allocates the number of 4KB pages specified by Pages of a certain memory type
59 with an alignment specified by Alignment. The allocated buffer is returned.
60 If Pages is 0, then NULL is returned. If there is not enough memory at the
61 specified alignment remaining to satisfy the request, then NULL is returned.
62 If Alignment is not a power of two and Alignment is not zero, then ASSERT().
63 If Pages plus EFI_SIZE_TO_PAGES (Alignment) overflows, then ASSERT().
64
65 @param MemoryType The type of memory to allocate.
66 @param Pages The number of 4 KB pages to allocate.
67 @param Alignment The requested alignment of the allocation.
68 Must be a power of two.
69 If Alignment is zero, then byte alignment is
70 used.
71
72 @return A pointer to the allocated buffer or NULL if allocation fails.
73
74 **/
75 STATIC
76 VOID *
77 InternalAllocateAlignedPages (
78 IN EFI_MEMORY_TYPE MemoryType,
79 IN UINTN Pages,
80 IN UINTN Alignment
81 )
82 {
83 EFI_STATUS Status;
84 EFI_PHYSICAL_ADDRESS Memory;
85 UINTN AlignedMemory;
86 UINTN AlignmentMask;
87 UINTN UnalignedPages;
88 UINTN RealPages;
89
90 //
91 // Alignment must be a power of two or zero.
92 //
93 ASSERT ((Alignment & (Alignment - 1)) == 0);
94
95 if (Pages == 0) {
96 return NULL;
97 }
98 if (Alignment > EFI_PAGE_SIZE) {
99 //
100 // Calculate the total number of pages since alignment is larger than page
101 // size.
102 //
103 AlignmentMask = Alignment - 1;
104 RealPages = Pages + EFI_SIZE_TO_PAGES (Alignment);
105 //
106 // Make sure that Pages plus EFI_SIZE_TO_PAGES (Alignment) does not
107 // overflow.
108 //
109 ASSERT (RealPages > Pages);
110
111 Memory = mDmaHostAddressLimit;
112 Status = gBS->AllocatePages (AllocateMaxAddress, MemoryType, RealPages,
113 &Memory);
114 if (EFI_ERROR (Status)) {
115 return NULL;
116 }
117 AlignedMemory = ((UINTN)Memory + AlignmentMask) & ~AlignmentMask;
118 UnalignedPages = EFI_SIZE_TO_PAGES (AlignedMemory - (UINTN)Memory);
119 if (UnalignedPages > 0) {
120 //
121 // Free first unaligned page(s).
122 //
123 Status = gBS->FreePages (Memory, UnalignedPages);
124 ASSERT_EFI_ERROR (Status);
125 }
126 Memory = AlignedMemory + EFI_PAGES_TO_SIZE (Pages);
127 UnalignedPages = RealPages - Pages - UnalignedPages;
128 if (UnalignedPages > 0) {
129 //
130 // Free last unaligned page(s).
131 //
132 Status = gBS->FreePages (Memory, UnalignedPages);
133 ASSERT_EFI_ERROR (Status);
134 }
135 } else {
136 //
137 // Do not over-allocate pages in this case.
138 //
139 Memory = mDmaHostAddressLimit;
140 Status = gBS->AllocatePages (AllocateMaxAddress, MemoryType, Pages,
141 &Memory);
142 if (EFI_ERROR (Status)) {
143 return NULL;
144 }
145 AlignedMemory = (UINTN)Memory;
146 }
147 return (VOID *)AlignedMemory;
148 }
149
150 /**
151 Provides the DMA controller-specific addresses needed to access system memory.
152
153 Operation is relative to the DMA bus master.
154
155 @param Operation Indicates if the bus master is going to read or
156 write to system memory.
157 @param HostAddress The system memory address to map to the DMA
158 controller.
159 @param NumberOfBytes On input the number of bytes to map. On output
160 the number of bytes that were mapped.
161 @param DeviceAddress The resulting map address for the bus master
162 controller to use to access the host's
163 HostAddress.
164 @param Mapping A resulting value to pass to Unmap().
165
166 @retval EFI_SUCCESS The range was mapped for the returned
167 NumberOfBytes.
168 @retval EFI_UNSUPPORTED The HostAddress cannot be mapped as a common
169 buffer.
170 @retval EFI_INVALID_PARAMETER One or more parameters are invalid.
171 @retval EFI_OUT_OF_RESOURCES The request could not be completed due to a lack
172 of resources.
173 @retval EFI_DEVICE_ERROR The system hardware could not map the requested
174 address.
175
176 **/
177 EFI_STATUS
178 EFIAPI
179 DmaMap (
180 IN DMA_MAP_OPERATION Operation,
181 IN VOID *HostAddress,
182 IN OUT UINTN *NumberOfBytes,
183 OUT PHYSICAL_ADDRESS *DeviceAddress,
184 OUT VOID **Mapping
185 )
186 {
187 EFI_STATUS Status;
188 MAP_INFO_INSTANCE *Map;
189 VOID *Buffer;
190 EFI_GCD_MEMORY_SPACE_DESCRIPTOR GcdDescriptor;
191 UINTN AllocSize;
192
193 if (HostAddress == NULL ||
194 NumberOfBytes == NULL ||
195 DeviceAddress == NULL ||
196 Mapping == NULL ) {
197 return EFI_INVALID_PARAMETER;
198 }
199
200 if (Operation >= MapOperationMaximum) {
201 return EFI_INVALID_PARAMETER;
202 }
203
204 *DeviceAddress = HostToDeviceAddress (HostAddress);
205
206 // Remember range so we can flush on the other side
207 Map = AllocatePool (sizeof (MAP_INFO_INSTANCE));
208 if (Map == NULL) {
209 return EFI_OUT_OF_RESOURCES;
210 }
211
212 if (((UINTN)HostAddress + *NumberOfBytes) > mDmaHostAddressLimit) {
213
214 if (Operation == MapOperationBusMasterCommonBuffer) {
215 goto CommonBufferError;
216 }
217
218 AllocSize = ALIGN_VALUE (*NumberOfBytes, mCpu->DmaBufferAlignment);
219 Map->BufferAddress = InternalAllocateAlignedPages (EfiBootServicesData,
220 EFI_SIZE_TO_PAGES (AllocSize),
221 mCpu->DmaBufferAlignment);
222 if (Map->BufferAddress == NULL) {
223 Status = EFI_OUT_OF_RESOURCES;
224 goto FreeMapInfo;
225 }
226
227 if (Map->Operation == MapOperationBusMasterRead) {
228 CopyMem (Map->BufferAddress, (VOID *)(UINTN)Map->HostAddress,
229 *NumberOfBytes);
230 }
231 mCpu->FlushDataCache (mCpu, (UINTN)Map->BufferAddress, AllocSize,
232 EfiCpuFlushTypeWriteBack);
233
234 *DeviceAddress = HostToDeviceAddress (Map->BufferAddress);
235 } else if (Operation != MapOperationBusMasterRead &&
236 ((((UINTN)HostAddress & (mCpu->DmaBufferAlignment - 1)) != 0) ||
237 ((*NumberOfBytes & (mCpu->DmaBufferAlignment - 1)) != 0))) {
238
239 // Get the cacheability of the region
240 Status = gDS->GetMemorySpaceDescriptor ((UINTN)HostAddress, &GcdDescriptor);
241 if (EFI_ERROR(Status)) {
242 goto FreeMapInfo;
243 }
244
245 // If the mapped buffer is not an uncached buffer
246 if ((GcdDescriptor.Attributes & (EFI_MEMORY_WB | EFI_MEMORY_WT)) != 0) {
247 //
248 // Operations of type MapOperationBusMasterCommonBuffer are only allowed
249 // on uncached buffers.
250 //
251 if (Operation == MapOperationBusMasterCommonBuffer) {
252 goto CommonBufferError;
253 }
254
255 //
256 // If the buffer does not fill entire cache lines we must double buffer
257 // into a suitably aligned allocation that allows us to invalidate the
258 // cache without running the risk of corrupting adjacent unrelated data.
259 // Note that pool allocations are guaranteed to be 8 byte aligned, so
260 // we only have to add (alignment - 8) worth of padding.
261 //
262 Map->DoubleBuffer = TRUE;
263 AllocSize = ALIGN_VALUE (*NumberOfBytes, mCpu->DmaBufferAlignment) +
264 (mCpu->DmaBufferAlignment - 8);
265 Map->BufferAddress = AllocatePool (AllocSize);
266 if (Map->BufferAddress == NULL) {
267 Status = EFI_OUT_OF_RESOURCES;
268 goto FreeMapInfo;
269 }
270
271 Buffer = ALIGN_POINTER (Map->BufferAddress, mCpu->DmaBufferAlignment);
272 *DeviceAddress = HostToDeviceAddress (Buffer);
273
274 //
275 // Get rid of any dirty cachelines covering the double buffer. This
276 // prevents them from being written back unexpectedly, potentially
277 // overwriting the data we receive from the device.
278 //
279 mCpu->FlushDataCache (mCpu, (UINTN)Buffer, *NumberOfBytes,
280 EfiCpuFlushTypeWriteBack);
281 } else {
282 Map->DoubleBuffer = FALSE;
283 }
284 } else {
285 Map->DoubleBuffer = FALSE;
286
287 DEBUG_CODE_BEGIN ();
288
289 //
290 // The operation type check above only executes if the buffer happens to be
291 // misaligned with respect to CWG, but even if it is aligned, we should not
292 // allow arbitrary buffers to be used for creating consistent mappings.
293 // So duplicate the check here when running in DEBUG mode, just to assert
294 // that we are not trying to create a consistent mapping for cached memory.
295 //
296 Status = gDS->GetMemorySpaceDescriptor ((UINTN)HostAddress, &GcdDescriptor);
297 ASSERT_EFI_ERROR(Status);
298
299 ASSERT (Operation != MapOperationBusMasterCommonBuffer ||
300 (GcdDescriptor.Attributes & (EFI_MEMORY_WB | EFI_MEMORY_WT)) == 0);
301
302 DEBUG_CODE_END ();
303
304 // Flush the Data Cache (should not have any effect if the memory region is
305 // uncached)
306 mCpu->FlushDataCache (mCpu, (UINTN)HostAddress, *NumberOfBytes,
307 EfiCpuFlushTypeWriteBackInvalidate);
308 }
309
310 Map->HostAddress = (UINTN)HostAddress;
311 Map->NumberOfBytes = *NumberOfBytes;
312 Map->Operation = Operation;
313
314 *Mapping = Map;
315
316 return EFI_SUCCESS;
317
318 CommonBufferError:
319 DEBUG ((DEBUG_ERROR,
320 "%a: Operation type 'MapOperationBusMasterCommonBuffer' is only "
321 "supported\non memory regions that were allocated using "
322 "DmaAllocateBuffer ()\n", __FUNCTION__));
323 Status = EFI_UNSUPPORTED;
324 FreeMapInfo:
325 FreePool (Map);
326
327 return Status;
328 }
329
330
331 /**
332 Completes the DmaMapBusMasterRead(), DmaMapBusMasterWrite(), or
333 DmaMapBusMasterCommonBuffer() operation and releases any corresponding
334 resources.
335
336 @param Mapping The mapping value returned from DmaMap*().
337
338 @retval EFI_SUCCESS The range was unmapped.
339 @retval EFI_DEVICE_ERROR The data was not committed to the target system
340 memory.
341 @retval EFI_INVALID_PARAMETER An inconsistency was detected between the
342 mapping type and the DoubleBuffer field
343
344 **/
345 EFI_STATUS
346 EFIAPI
347 DmaUnmap (
348 IN VOID *Mapping
349 )
350 {
351 MAP_INFO_INSTANCE *Map;
352 EFI_STATUS Status;
353 VOID *Buffer;
354 UINTN AllocSize;
355
356 if (Mapping == NULL) {
357 ASSERT (FALSE);
358 return EFI_INVALID_PARAMETER;
359 }
360
361 Map = (MAP_INFO_INSTANCE *)Mapping;
362
363 Status = EFI_SUCCESS;
364 if (((UINTN)Map->HostAddress + Map->NumberOfBytes) > mDmaHostAddressLimit) {
365 AllocSize = ALIGN_VALUE (Map->NumberOfBytes, mCpu->DmaBufferAlignment);
366 if (Map->Operation == MapOperationBusMasterWrite) {
367 mCpu->FlushDataCache (mCpu, (UINTN)Map->BufferAddress, AllocSize,
368 EfiCpuFlushTypeInvalidate);
369 CopyMem ((VOID *)(UINTN)Map->HostAddress, Map->BufferAddress,
370 Map->NumberOfBytes);
371 }
372 FreePages (Map->BufferAddress, EFI_SIZE_TO_PAGES (AllocSize));
373 } else if (Map->DoubleBuffer) {
374
375 ASSERT (Map->Operation == MapOperationBusMasterWrite);
376
377 if (Map->Operation != MapOperationBusMasterWrite) {
378 Status = EFI_INVALID_PARAMETER;
379 } else {
380 Buffer = ALIGN_POINTER (Map->BufferAddress, mCpu->DmaBufferAlignment);
381
382 mCpu->FlushDataCache (mCpu, (UINTN)Buffer, Map->NumberOfBytes,
383 EfiCpuFlushTypeInvalidate);
384
385 CopyMem ((VOID *)(UINTN)Map->HostAddress, Buffer, Map->NumberOfBytes);
386
387 FreePool (Map->BufferAddress);
388 }
389 } else {
390 if (Map->Operation == MapOperationBusMasterWrite) {
391 //
392 // Make sure we read buffer from uncached memory and not the cache
393 //
394 mCpu->FlushDataCache (mCpu, Map->HostAddress, Map->NumberOfBytes,
395 EfiCpuFlushTypeInvalidate);
396 }
397 }
398
399 FreePool (Map);
400
401 return Status;
402 }
403
404 /**
405 Allocates pages that are suitable for an DmaMap() of type
406 MapOperationBusMasterCommonBuffer mapping.
407
408 @param MemoryType The type of memory to allocate,
409 EfiBootServicesData or EfiRuntimeServicesData.
410 @param Pages The number of pages to allocate.
411 @param HostAddress A pointer to store the base system memory
412 address of the allocated range.
413
414 @retval EFI_SUCCESS The requested memory pages were allocated.
415 @retval EFI_INVALID_PARAMETER One or more parameters are invalid.
416 @retval EFI_OUT_OF_RESOURCES The memory pages could not be allocated.
417
418 **/
419 EFI_STATUS
420 EFIAPI
421 DmaAllocateBuffer (
422 IN EFI_MEMORY_TYPE MemoryType,
423 IN UINTN Pages,
424 OUT VOID **HostAddress
425 )
426 {
427 return DmaAllocateAlignedBuffer (MemoryType, Pages, 0, HostAddress);
428 }
429
430 /**
431 Allocates pages that are suitable for an DmaMap() of type
432 MapOperationBusMasterCommonBuffer mapping, at the requested alignment.
433
434 @param MemoryType The type of memory to allocate,
435 EfiBootServicesData or EfiRuntimeServicesData.
436 @param Pages The number of pages to allocate.
437 @param Alignment Alignment in bytes of the base of the returned
438 buffer (must be a power of 2)
439 @param HostAddress A pointer to store the base system memory
440 address of the allocated range.
441
442 @retval EFI_SUCCESS The requested memory pages were allocated.
443 @retval EFI_INVALID_PARAMETER One or more parameters are invalid.
444 @retval EFI_OUT_OF_RESOURCES The memory pages could not be allocated.
445
446 **/
447 EFI_STATUS
448 EFIAPI
449 DmaAllocateAlignedBuffer (
450 IN EFI_MEMORY_TYPE MemoryType,
451 IN UINTN Pages,
452 IN UINTN Alignment,
453 OUT VOID **HostAddress
454 )
455 {
456 EFI_GCD_MEMORY_SPACE_DESCRIPTOR GcdDescriptor;
457 VOID *Allocation;
458 UINT64 MemType;
459 UNCACHED_ALLOCATION *Alloc;
460 EFI_STATUS Status;
461
462 if (Alignment == 0) {
463 Alignment = EFI_PAGE_SIZE;
464 }
465
466 if (HostAddress == NULL ||
467 (Alignment & (Alignment - 1)) != 0) {
468 return EFI_INVALID_PARAMETER;
469 }
470
471 if (MemoryType == EfiBootServicesData ||
472 MemoryType == EfiRuntimeServicesData) {
473 Allocation = InternalAllocateAlignedPages (MemoryType, Pages, Alignment);
474 } else {
475 return EFI_INVALID_PARAMETER;
476 }
477
478 if (Allocation == NULL) {
479 return EFI_OUT_OF_RESOURCES;
480 }
481
482 // Get the cacheability of the region
483 Status = gDS->GetMemorySpaceDescriptor ((UINTN)Allocation, &GcdDescriptor);
484 if (EFI_ERROR(Status)) {
485 goto FreeBuffer;
486 }
487
488 // Choose a suitable uncached memory type that is supported by the region
489 if (GcdDescriptor.Capabilities & EFI_MEMORY_WC) {
490 MemType = EFI_MEMORY_WC;
491 } else if (GcdDescriptor.Capabilities & EFI_MEMORY_UC) {
492 MemType = EFI_MEMORY_UC;
493 } else {
494 Status = EFI_UNSUPPORTED;
495 goto FreeBuffer;
496 }
497
498 Alloc = AllocatePool (sizeof *Alloc);
499 if (Alloc == NULL) {
500 goto FreeBuffer;
501 }
502
503 Alloc->HostAddress = Allocation;
504 Alloc->NumPages = Pages;
505 Alloc->Attributes = GcdDescriptor.Attributes;
506
507 InsertHeadList (&UncachedAllocationList, &Alloc->Link);
508
509 // Remap the region with the new attributes
510 Status = gDS->SetMemorySpaceAttributes ((PHYSICAL_ADDRESS)(UINTN)Allocation,
511 EFI_PAGES_TO_SIZE (Pages),
512 MemType);
513 if (EFI_ERROR (Status)) {
514 goto FreeAlloc;
515 }
516
517 Status = mCpu->FlushDataCache (mCpu,
518 (PHYSICAL_ADDRESS)(UINTN)Allocation,
519 EFI_PAGES_TO_SIZE (Pages),
520 EfiCpuFlushTypeInvalidate);
521 if (EFI_ERROR (Status)) {
522 goto FreeAlloc;
523 }
524
525 *HostAddress = Allocation;
526
527 return EFI_SUCCESS;
528
529 FreeAlloc:
530 RemoveEntryList (&Alloc->Link);
531 FreePool (Alloc);
532
533 FreeBuffer:
534 FreePages (Allocation, Pages);
535 return Status;
536 }
537
538
539 /**
540 Frees memory that was allocated with DmaAllocateBuffer().
541
542 @param Pages The number of pages to free.
543 @param HostAddress The base system memory address of the allocated
544 range.
545
546 @retval EFI_SUCCESS The requested memory pages were freed.
547 @retval EFI_INVALID_PARAMETER The memory range specified by HostAddress and
548 Pages was not allocated with
549 DmaAllocateBuffer().
550
551 **/
552 EFI_STATUS
553 EFIAPI
554 DmaFreeBuffer (
555 IN UINTN Pages,
556 IN VOID *HostAddress
557 )
558 {
559 LIST_ENTRY *Link;
560 UNCACHED_ALLOCATION *Alloc;
561 BOOLEAN Found;
562 EFI_STATUS Status;
563
564 if (HostAddress == NULL) {
565 return EFI_INVALID_PARAMETER;
566 }
567
568 for (Link = GetFirstNode (&UncachedAllocationList), Found = FALSE;
569 !IsNull (&UncachedAllocationList, Link);
570 Link = GetNextNode (&UncachedAllocationList, Link)) {
571
572 Alloc = BASE_CR (Link, UNCACHED_ALLOCATION, Link);
573 if (Alloc->HostAddress == HostAddress && Alloc->NumPages == Pages) {
574 Found = TRUE;
575 break;
576 }
577 }
578
579 if (!Found) {
580 ASSERT (FALSE);
581 return EFI_INVALID_PARAMETER;
582 }
583
584 RemoveEntryList (&Alloc->Link);
585
586 Status = gDS->SetMemorySpaceAttributes ((PHYSICAL_ADDRESS)(UINTN)HostAddress,
587 EFI_PAGES_TO_SIZE (Pages),
588 Alloc->Attributes);
589 if (EFI_ERROR (Status)) {
590 goto FreeAlloc;
591 }
592
593 //
594 // If we fail to restore the original attributes, it is better to leak the
595 // memory than to return it to the heap
596 //
597 FreePages (HostAddress, Pages);
598
599 FreeAlloc:
600 FreePool (Alloc);
601 return Status;
602 }
603
604
605 EFI_STATUS
606 EFIAPI
607 NonCoherentDmaLibConstructor (
608 IN EFI_HANDLE ImageHandle,
609 IN EFI_SYSTEM_TABLE *SystemTable
610 )
611 {
612 InitializeListHead (&UncachedAllocationList);
613
614 //
615 // Ensure that the combination of DMA addressing offset and limit produces
616 // a sane value.
617 //
618 ASSERT (PcdGet64 (PcdDmaDeviceLimit) > PcdGet64 (PcdDmaDeviceOffset));
619
620 mDmaHostAddressLimit = PcdGet64 (PcdDmaDeviceLimit) -
621 PcdGet64 (PcdDmaDeviceOffset);
622
623 // Get the Cpu protocol for later use
624 return gBS->LocateProtocol (&gEfiCpuArchProtocolGuid, NULL, (VOID **)&mCpu);
625 }