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