summaryrefslogtreecommitdiff
path: root/OvmfPkg/Library/MapMmioLib/MapMmioLib.c
blob: 56042ca8554d38e07011da37dc8a8b4064772524 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
/** @file
  Helper library to map mmio memory regions.

  Copyright (c) 2026, Arm Ltd. All rights reserved.<BR>
  SPDX-License-Identifier: BSD-2-Clause-Patent
**/

#include <Base.h>
#include <Uefi.h>

#include <Library/BaseLib.h>
#include <Library/DebugLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/DxeServicesTableLib.h>
#include <Library/UefiBootServicesTableLib.h>

STATIC EFI_EVENT  mExitBootServicesEvent;
STATIC BOOLEAN    mAtRuntime = FALSE;

/**
  Ensure a range is present in the GCD memory space map as MMIO.

  The input range must already be page-aligned. The function walks the current
  GCD memory space map and adds every overlapping EfiGcdMemoryTypeNonExistent
  descriptor as EfiGcdMemoryTypeMemoryMappedIo with the requested attributes.
  Existing EfiGcdMemoryTypeMemoryMappedIo descriptors are accepted only when
  their capabilities contain all requested attributes. Existing descriptors of
  any other type, or MMIO descriptors without the requested attributes, are
  treated as conflicts.

  This function only ensures that MMIO GCD descriptors exist. It does not set
  memory space attributes.

  @param[in] Base        The page-aligned base address of the MMIO range.
  @param[in] Length      The page-aligned size of the MMIO range, in bytes.
  @param[in] Attributes  The GCD memory space attributes required for the MMIO
                         range.

  @retval EFI_SUCCESS      The range is backed by compatible MMIO descriptors.
  @retval EFI_UNSUPPORTED  The range overlaps an existing non-MMIO descriptor,
                           or an MMIO descriptor without the requested
                           attributes.
  @retval EFI_ABORTED      An existing GCD descriptor is malformed.
  @retval Others           The GCD memory services returned an error.
**/
STATIC
EFI_STATUS
AddMmioMemorySpace (
  IN  UINT64  Base,
  IN  UINT64  Length,
  IN  UINT64  Attributes
  )
{
  EFI_STATUS                       Status;
  UINTN                            Index;
  UINTN                            NumberOfDescriptors;
  EFI_GCD_MEMORY_SPACE_DESCRIPTOR  *MemorySpaceMap;
  EFI_GCD_MEMORY_SPACE_DESCRIPTOR  *Descriptor;
  UINT64                           IntersectionBase;
  UINT64                           IntersectionEnd;

  Status = gDS->GetMemorySpaceMap (&NumberOfDescriptors, &MemorySpaceMap);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  for (Index = 0; Index < NumberOfDescriptors; Index++) {
    Descriptor = &MemorySpaceMap[Index];

    if (Descriptor->BaseAddress > (MAX_UINT64 - Descriptor->Length)) {
      Status = EFI_ABORTED;
      break;
    }

    IntersectionBase = MAX (Base, Descriptor->BaseAddress);
    IntersectionEnd  = MIN (
                         Base + Length,
                         Descriptor->BaseAddress + Descriptor->Length
                         );
    if (IntersectionBase >= IntersectionEnd) {
      //
      // The descriptor and the aperture don't overlap.
      //
      continue;
    }

    if (Descriptor->GcdMemoryType == EfiGcdMemoryTypeNonExistent) {
      Status = gDS->AddMemorySpace (
                      EfiGcdMemoryTypeMemoryMappedIo,
                      IntersectionBase,
                      IntersectionEnd - IntersectionBase,
                      Attributes
                      );

      DEBUG ((
        EFI_ERROR (Status) ? DEBUG_ERROR : DEBUG_VERBOSE,
        "%a: %a: add [%Lx, %Lx): %r\n",
        gEfiCallerBaseName,
        __func__,
        IntersectionBase,
        IntersectionEnd,
        Status
        ));
      if (EFI_ERROR (Status)) {
        break;
      }

      continue;
    }

    if ((Descriptor->GcdMemoryType != EfiGcdMemoryTypeMemoryMappedIo) ||
        ((Descriptor->Capabilities & Attributes) != Attributes))
    {
      Status = EFI_UNSUPPORTED;
      break;
    }
  } // for

  FreePool (MemorySpaceMap);
  return Status;
}

/**
  Map a range as MMIO in the GCD memory map.

  The requested range is expanded to page boundaries before it is processed.
  Missing GCD memory space descriptors are added as
  EfiGcdMemoryTypeMemoryMappedIo. Existing MMIO descriptors are accepted only
  when their capabilities contain the requested attributes. Existing descriptors
  of any other type are treated as conflicts.

  After the range is backed by compatible MMIO descriptors, the requested GCD
  memory space attributes are applied to the normalized full range.

  If this function fails after adding new GCD MMIO descriptors, the descriptors
  are not rolled back. Callers are expected to treat failures from this function
  as fatal to the current boot path.

  @param[in] Base        The base address of the requested MMIO range.
  @param[in] Length      The size of the requested MMIO range, in bytes.
  @param[in] Attributes  The GCD memory space attributes to apply to the MMIO
                         range.

  @retval EFI_SUCCESS            The full range was mapped as MMIO and
                                 configured with the requested attributes.
  @retval EFI_INVALID_PARAMETER  Length is zero, or the normalized range
                                 overflows the physical address space.
  @retval EFI_UNSUPPORTED        The range overlaps an existing non-MMIO
                                 descriptor, or an MMIO descriptor without the
                                 requested attributes.
  @retval EFI_ABORTED            An existing GCD descriptor is malformed.
  @retval EFI_ACCESS_DENIED      DXE Services are no longer available.
  @retval Others                 The GCD memory services returned an error.
**/
EFI_STATUS
EFIAPI
MapMmioMemory (
  IN EFI_PHYSICAL_ADDRESS  Base,
  IN UINT64                Length,
  IN UINT64                Attributes
  )
{
  EFI_STATUS            Status;
  EFI_PHYSICAL_ADDRESS  RegionEnd;

  if (mAtRuntime) {
    //
    // DXE Services are no longer available.
    //
    return EFI_ACCESS_DENIED;
  }

  DEBUG ((
    DEBUG_INFO,
    "Map MMIO Memory: 0x%08lx - 0x%08lx : 0x%08lx\n",
    Base,
    Length,
    Attributes
    ));

  if (Length == 0) {
    return EFI_INVALID_PARAMETER;
  }

  // Check if RegionsBase + Length would overflow
  if ((Base > (MAX_UINT64 - Length))) {
    return EFI_INVALID_PARAMETER;
  }

  RegionEnd = Base + Length;

  // Check if aligning RegionEnd would overflow
  if (RegionEnd > MAX_UINT64 - ALIGN_VALUE_ADDEND (RegionEnd, EFI_PAGE_SIZE)) {
    return EFI_INVALID_PARAMETER;
  }

  RegionEnd = ALIGN_VALUE (RegionEnd, EFI_PAGE_SIZE);

  // Align down Base to page boundary
  Base = Base & ~(EFI_PAGE_SIZE - 1);

  // Calculate the total region size.
  Length = RegionEnd - Base;

  Status = AddMmioMemorySpace (Base, Length, Attributes);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  return gDS->SetMemorySpaceAttributes (Base, Length, Attributes);
}

/**
  Notification function signaled when ExitBootServices() is called.

  Record that DXE services are no longer available. MapMmioMemory() uses this
  state to reject calls after ExitBootServices().

  @param[in] Event    Event whose notification function is being invoked.
  @param[in] Context  Pointer to the notification function's context.
**/
STATIC
VOID
EFIAPI
MapMmioLibExitBootServicesNotify (
  IN EFI_EVENT  Event,
  IN VOID       *Context
  )
{
  mAtRuntime = TRUE;
}

/**
  Library instance destructor.

  Close the ExitBootServices event created by the constructor.

  @param[in] ImageHandle  The firmware allocated handle for the EFI image.
  @param[in] SystemTable  A pointer to the EFI System Table.

  @retval EFI_SUCCESS  The ExitBootServices event was closed.
  @retval Others       Failed to close the ExitBootServices event.
**/
EFI_STATUS
EFIAPI
MapMmioLibDestructor (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  return gBS->CloseEvent (mExitBootServicesEvent);
}

/**
  Library instance constructor.

  Register for ExitBootServices() notification so MapMmioLib can detect when
  DXE services are no longer available.

  @param[in] ImageHandle  The firmware allocated handle for the EFI image.
  @param[in] SystemTable  A pointer to the EFI System Table.

  @retval EFI_SUCCESS  The ExitBootServices event was registered.
  @retval Others       Failed to register the ExitBootServices event.
**/
EFI_STATUS
EFIAPI
MapMmioLibConstructor (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  return gBS->CreateEventEx (
                EVT_NOTIFY_SIGNAL,
                TPL_CALLBACK,
                MapMmioLibExitBootServicesNotify,
                NULL,
                &gEfiEventExitBootServicesGuid,
                &mExitBootServicesEvent
                );
}