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