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