]> git.proxmox.com Git - mirror_edk2.git/blob - EmbeddedPkg/Library/NonCoherentDmaLib/NonCoherentDmaLib.c
EmbeddedPkg: implement NonCoherentDmaLib based on ArmDmaLib
[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 This program and the accompanying materials are licensed and made
9 available under the terms and conditions of the BSD License which
10 accompanies this distribution. The full text of the license may be
11 found at http://opensource.org/licenses/bsd-license.php
12
13 THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
14 WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR
15 IMPLIED.
16
17 **/
18
19 #include <PiDxe.h>
20 #include <Library/BaseLib.h>
21 #include <Library/DebugLib.h>
22 #include <Library/DmaLib.h>
23 #include <Library/DxeServicesTableLib.h>
24 #include <Library/MemoryAllocationLib.h>
25 #include <Library/UefiBootServicesTableLib.h>
26 #include <Library/IoLib.h>
27 #include <Library/BaseMemoryLib.h>
28
29 #include <Protocol/Cpu.h>
30
31 typedef struct {
32 EFI_PHYSICAL_ADDRESS HostAddress;
33 VOID *BufferAddress;
34 UINTN NumberOfBytes;
35 DMA_MAP_OPERATION Operation;
36 BOOLEAN DoubleBuffer;
37 } MAP_INFO_INSTANCE;
38
39
40 typedef struct {
41 LIST_ENTRY Link;
42 VOID *HostAddress;
43 UINTN NumPages;
44 UINT64 Attributes;
45 } UNCACHED_ALLOCATION;
46
47 STATIC EFI_CPU_ARCH_PROTOCOL *mCpu;
48 STATIC LIST_ENTRY UncachedAllocationList;
49
50 STATIC
51 PHYSICAL_ADDRESS
52 HostToDeviceAddress (
53 IN VOID *Address
54 )
55 {
56 return (PHYSICAL_ADDRESS)(UINTN)Address + PcdGet64 (PcdDmaDeviceOffset);
57 }
58
59 /**
60 Provides the DMA controller-specific addresses needed to access system memory.
61
62 Operation is relative to the DMA bus master.
63
64 @param Operation Indicates if the bus master is going to read or
65 write to system memory.
66 @param HostAddress The system memory address to map to the DMA
67 controller.
68 @param NumberOfBytes On input the number of bytes to map. On output
69 the number of bytes that were mapped.
70 @param DeviceAddress The resulting map address for the bus master
71 controller to use to access the host's
72 HostAddress.
73 @param Mapping A resulting value to pass to Unmap().
74
75 @retval EFI_SUCCESS The range was mapped for the returned
76 NumberOfBytes.
77 @retval EFI_UNSUPPORTED The HostAddress cannot be mapped as a common
78 buffer.
79 @retval EFI_INVALID_PARAMETER One or more parameters are invalid.
80 @retval EFI_OUT_OF_RESOURCES The request could not be completed due to a lack
81 of resources.
82 @retval EFI_DEVICE_ERROR The system hardware could not map the requested
83 address.
84
85 **/
86 EFI_STATUS
87 EFIAPI
88 DmaMap (
89 IN DMA_MAP_OPERATION Operation,
90 IN VOID *HostAddress,
91 IN OUT UINTN *NumberOfBytes,
92 OUT PHYSICAL_ADDRESS *DeviceAddress,
93 OUT VOID **Mapping
94 )
95 {
96 EFI_STATUS Status;
97 MAP_INFO_INSTANCE *Map;
98 VOID *Buffer;
99 EFI_GCD_MEMORY_SPACE_DESCRIPTOR GcdDescriptor;
100 UINTN AllocSize;
101
102 if (HostAddress == NULL ||
103 NumberOfBytes == NULL ||
104 DeviceAddress == NULL ||
105 Mapping == NULL ) {
106 return EFI_INVALID_PARAMETER;
107 }
108
109 if (Operation >= MapOperationMaximum) {
110 return EFI_INVALID_PARAMETER;
111 }
112
113 *DeviceAddress = HostToDeviceAddress (HostAddress);
114
115 // Remember range so we can flush on the other side
116 Map = AllocatePool (sizeof (MAP_INFO_INSTANCE));
117 if (Map == NULL) {
118 return EFI_OUT_OF_RESOURCES;
119 }
120
121 if (Operation != MapOperationBusMasterRead &&
122 ((((UINTN)HostAddress & (mCpu->DmaBufferAlignment - 1)) != 0) ||
123 ((*NumberOfBytes & (mCpu->DmaBufferAlignment - 1)) != 0))) {
124
125 // Get the cacheability of the region
126 Status = gDS->GetMemorySpaceDescriptor ((UINTN)HostAddress, &GcdDescriptor);
127 if (EFI_ERROR(Status)) {
128 goto FreeMapInfo;
129 }
130
131 // If the mapped buffer is not an uncached buffer
132 if ((GcdDescriptor.Attributes & (EFI_MEMORY_WB | EFI_MEMORY_WT)) != 0) {
133 //
134 // Operations of type MapOperationBusMasterCommonBuffer are only allowed
135 // on uncached buffers.
136 //
137 if (Operation == MapOperationBusMasterCommonBuffer) {
138 DEBUG ((DEBUG_ERROR,
139 "%a: Operation type 'MapOperationBusMasterCommonBuffer' is only "
140 "supported\non memory regions that were allocated using "
141 "DmaAllocateBuffer ()\n", __FUNCTION__));
142 Status = EFI_UNSUPPORTED;
143 goto FreeMapInfo;
144 }
145
146 //
147 // If the buffer does not fill entire cache lines we must double buffer
148 // into a suitably aligned allocation that allows us to invalidate the
149 // cache without running the risk of corrupting adjacent unrelated data.
150 // Note that pool allocations are guaranteed to be 8 byte aligned, so
151 // we only have to add (alignment - 8) worth of padding.
152 //
153 Map->DoubleBuffer = TRUE;
154 AllocSize = ALIGN_VALUE (*NumberOfBytes, mCpu->DmaBufferAlignment) +
155 (mCpu->DmaBufferAlignment - 8);
156 Map->BufferAddress = AllocatePool (AllocSize);
157 if (Map->BufferAddress == NULL) {
158 Status = EFI_OUT_OF_RESOURCES;
159 goto FreeMapInfo;
160 }
161
162 Buffer = ALIGN_POINTER (Map->BufferAddress, mCpu->DmaBufferAlignment);
163 *DeviceAddress = HostToDeviceAddress (Buffer);
164
165 //
166 // Get rid of any dirty cachelines covering the double buffer. This
167 // prevents them from being written back unexpectedly, potentially
168 // overwriting the data we receive from the device.
169 //
170 mCpu->FlushDataCache (mCpu, (UINTN)Buffer, *NumberOfBytes,
171 EfiCpuFlushTypeWriteBack);
172 } else {
173 Map->DoubleBuffer = FALSE;
174 }
175 } else {
176 Map->DoubleBuffer = FALSE;
177
178 DEBUG_CODE_BEGIN ();
179
180 //
181 // The operation type check above only executes if the buffer happens to be
182 // misaligned with respect to CWG, but even if it is aligned, we should not
183 // allow arbitrary buffers to be used for creating consistent mappings.
184 // So duplicate the check here when running in DEBUG mode, just to assert
185 // that we are not trying to create a consistent mapping for cached memory.
186 //
187 Status = gDS->GetMemorySpaceDescriptor ((UINTN)HostAddress, &GcdDescriptor);
188 ASSERT_EFI_ERROR(Status);
189
190 ASSERT (Operation != MapOperationBusMasterCommonBuffer ||
191 (GcdDescriptor.Attributes & (EFI_MEMORY_WB | EFI_MEMORY_WT)) == 0);
192
193 DEBUG_CODE_END ();
194
195 // Flush the Data Cache (should not have any effect if the memory region is
196 // uncached)
197 mCpu->FlushDataCache (mCpu, (UINTN)HostAddress, *NumberOfBytes,
198 EfiCpuFlushTypeWriteBackInvalidate);
199 }
200
201 Map->HostAddress = (UINTN)HostAddress;
202 Map->NumberOfBytes = *NumberOfBytes;
203 Map->Operation = Operation;
204
205 *Mapping = Map;
206
207 return EFI_SUCCESS;
208
209 FreeMapInfo:
210 FreePool (Map);
211
212 return Status;
213 }
214
215
216 /**
217 Completes the DmaMapBusMasterRead(), DmaMapBusMasterWrite(), or
218 DmaMapBusMasterCommonBuffer() operation and releases any corresponding
219 resources.
220
221 @param Mapping The mapping value returned from DmaMap*().
222
223 @retval EFI_SUCCESS The range was unmapped.
224 @retval EFI_DEVICE_ERROR The data was not committed to the target system
225 memory.
226 @retval EFI_INVALID_PARAMETER An inconsistency was detected between the
227 mapping type and the DoubleBuffer field
228
229 **/
230 EFI_STATUS
231 EFIAPI
232 DmaUnmap (
233 IN VOID *Mapping
234 )
235 {
236 MAP_INFO_INSTANCE *Map;
237 EFI_STATUS Status;
238 VOID *Buffer;
239
240 if (Mapping == NULL) {
241 ASSERT (FALSE);
242 return EFI_INVALID_PARAMETER;
243 }
244
245 Map = (MAP_INFO_INSTANCE *)Mapping;
246
247 Status = EFI_SUCCESS;
248 if (Map->DoubleBuffer) {
249 ASSERT (Map->Operation == MapOperationBusMasterWrite);
250
251 if (Map->Operation != MapOperationBusMasterWrite) {
252 Status = EFI_INVALID_PARAMETER;
253 } else {
254 Buffer = ALIGN_POINTER (Map->BufferAddress, mCpu->DmaBufferAlignment);
255
256 mCpu->FlushDataCache (mCpu, (UINTN)Buffer, Map->NumberOfBytes,
257 EfiCpuFlushTypeInvalidate);
258
259 CopyMem ((VOID *)(UINTN)Map->HostAddress, Buffer, Map->NumberOfBytes);
260
261 FreePool (Map->BufferAddress);
262 }
263 } else {
264 if (Map->Operation == MapOperationBusMasterWrite) {
265 //
266 // Make sure we read buffer from uncached memory and not the cache
267 //
268 mCpu->FlushDataCache (mCpu, Map->HostAddress, Map->NumberOfBytes,
269 EfiCpuFlushTypeInvalidate);
270 }
271 }
272
273 FreePool (Map);
274
275 return Status;
276 }
277
278 /**
279 Allocates pages that are suitable for an DmaMap() of type
280 MapOperationBusMasterCommonBuffer mapping.
281
282 @param MemoryType The type of memory to allocate,
283 EfiBootServicesData or EfiRuntimeServicesData.
284 @param Pages The number of pages to allocate.
285 @param HostAddress A pointer to store the base system memory
286 address of the allocated range.
287
288 @retval EFI_SUCCESS The requested memory pages were allocated.
289 @retval EFI_INVALID_PARAMETER One or more parameters are invalid.
290 @retval EFI_OUT_OF_RESOURCES The memory pages could not be allocated.
291
292 **/
293 EFI_STATUS
294 EFIAPI
295 DmaAllocateBuffer (
296 IN EFI_MEMORY_TYPE MemoryType,
297 IN UINTN Pages,
298 OUT VOID **HostAddress
299 )
300 {
301 return DmaAllocateAlignedBuffer (MemoryType, Pages, 0, HostAddress);
302 }
303
304 /**
305 Allocates pages that are suitable for an DmaMap() of type
306 MapOperationBusMasterCommonBuffer mapping, at the requested alignment.
307
308 @param MemoryType The type of memory to allocate,
309 EfiBootServicesData or EfiRuntimeServicesData.
310 @param Pages The number of pages to allocate.
311 @param Alignment Alignment in bytes of the base of the returned
312 buffer (must be a power of 2)
313 @param HostAddress A pointer to store the base system memory
314 address of the allocated range.
315
316 @retval EFI_SUCCESS The requested memory pages were allocated.
317 @retval EFI_INVALID_PARAMETER One or more parameters are invalid.
318 @retval EFI_OUT_OF_RESOURCES The memory pages could not be allocated.
319
320 **/
321 EFI_STATUS
322 EFIAPI
323 DmaAllocateAlignedBuffer (
324 IN EFI_MEMORY_TYPE MemoryType,
325 IN UINTN Pages,
326 IN UINTN Alignment,
327 OUT VOID **HostAddress
328 )
329 {
330 EFI_GCD_MEMORY_SPACE_DESCRIPTOR GcdDescriptor;
331 VOID *Allocation;
332 UINT64 MemType;
333 UNCACHED_ALLOCATION *Alloc;
334 EFI_STATUS Status;
335
336 if (Alignment == 0) {
337 Alignment = EFI_PAGE_SIZE;
338 }
339
340 if (HostAddress == NULL ||
341 (Alignment & (Alignment - 1)) != 0) {
342 return EFI_INVALID_PARAMETER;
343 }
344
345 if (MemoryType == EfiBootServicesData) {
346 Allocation = AllocateAlignedPages (Pages, Alignment);
347 } else if (MemoryType == EfiRuntimeServicesData) {
348 Allocation = AllocateAlignedRuntimePages (Pages, Alignment);
349 } else {
350 return EFI_INVALID_PARAMETER;
351 }
352
353 if (Allocation == NULL) {
354 return EFI_OUT_OF_RESOURCES;
355 }
356
357 // Get the cacheability of the region
358 Status = gDS->GetMemorySpaceDescriptor ((UINTN)Allocation, &GcdDescriptor);
359 if (EFI_ERROR(Status)) {
360 goto FreeBuffer;
361 }
362
363 // Choose a suitable uncached memory type that is supported by the region
364 if (GcdDescriptor.Capabilities & EFI_MEMORY_WC) {
365 MemType = EFI_MEMORY_WC;
366 } else if (GcdDescriptor.Capabilities & EFI_MEMORY_UC) {
367 MemType = EFI_MEMORY_UC;
368 } else {
369 Status = EFI_UNSUPPORTED;
370 goto FreeBuffer;
371 }
372
373 Alloc = AllocatePool (sizeof *Alloc);
374 if (Alloc == NULL) {
375 goto FreeBuffer;
376 }
377
378 Alloc->HostAddress = Allocation;
379 Alloc->NumPages = Pages;
380 Alloc->Attributes = GcdDescriptor.Attributes;
381
382 InsertHeadList (&UncachedAllocationList, &Alloc->Link);
383
384 // Remap the region with the new attributes
385 Status = gDS->SetMemorySpaceAttributes ((PHYSICAL_ADDRESS)(UINTN)Allocation,
386 EFI_PAGES_TO_SIZE (Pages),
387 MemType);
388 if (EFI_ERROR (Status)) {
389 goto FreeAlloc;
390 }
391
392 Status = mCpu->FlushDataCache (mCpu,
393 (PHYSICAL_ADDRESS)(UINTN)Allocation,
394 EFI_PAGES_TO_SIZE (Pages),
395 EfiCpuFlushTypeInvalidate);
396 if (EFI_ERROR (Status)) {
397 goto FreeAlloc;
398 }
399
400 *HostAddress = Allocation;
401
402 return EFI_SUCCESS;
403
404 FreeAlloc:
405 RemoveEntryList (&Alloc->Link);
406 FreePool (Alloc);
407
408 FreeBuffer:
409 FreePages (Allocation, Pages);
410 return Status;
411 }
412
413
414 /**
415 Frees memory that was allocated with DmaAllocateBuffer().
416
417 @param Pages The number of pages to free.
418 @param HostAddress The base system memory address of the allocated
419 range.
420
421 @retval EFI_SUCCESS The requested memory pages were freed.
422 @retval EFI_INVALID_PARAMETER The memory range specified by HostAddress and
423 Pages was not allocated with
424 DmaAllocateBuffer().
425
426 **/
427 EFI_STATUS
428 EFIAPI
429 DmaFreeBuffer (
430 IN UINTN Pages,
431 IN VOID *HostAddress
432 )
433 {
434 LIST_ENTRY *Link;
435 UNCACHED_ALLOCATION *Alloc;
436 BOOLEAN Found;
437 EFI_STATUS Status;
438
439 if (HostAddress == NULL) {
440 return EFI_INVALID_PARAMETER;
441 }
442
443 for (Link = GetFirstNode (&UncachedAllocationList), Found = FALSE;
444 !IsNull (&UncachedAllocationList, Link);
445 Link = GetNextNode (&UncachedAllocationList, Link)) {
446
447 Alloc = BASE_CR (Link, UNCACHED_ALLOCATION, Link);
448 if (Alloc->HostAddress == HostAddress && Alloc->NumPages == Pages) {
449 Found = TRUE;
450 break;
451 }
452 }
453
454 if (!Found) {
455 ASSERT (FALSE);
456 return EFI_INVALID_PARAMETER;
457 }
458
459 RemoveEntryList (&Alloc->Link);
460
461 Status = gDS->SetMemorySpaceAttributes ((PHYSICAL_ADDRESS)(UINTN)HostAddress,
462 EFI_PAGES_TO_SIZE (Pages),
463 Alloc->Attributes);
464 if (EFI_ERROR (Status)) {
465 goto FreeAlloc;
466 }
467
468 //
469 // If we fail to restore the original attributes, it is better to leak the
470 // memory than to return it to the heap
471 //
472 FreePages (HostAddress, Pages);
473
474 FreeAlloc:
475 FreePool (Alloc);
476 return Status;
477 }
478
479
480 EFI_STATUS
481 EFIAPI
482 NonCoherentDmaLibConstructor (
483 IN EFI_HANDLE ImageHandle,
484 IN EFI_SYSTEM_TABLE *SystemTable
485 )
486 {
487 InitializeListHead (&UncachedAllocationList);
488
489 // Get the Cpu protocol for later use
490 return gBS->LocateProtocol (&gEfiCpuArchProtocolGuid, NULL, (VOID **)&mCpu);
491 }