summaryrefslogtreecommitdiffstats
path: root/DynamicTablesPkg/Library/DynamicTablesScmiInfoLib/DynamicTablesScmiInfoLib.c
blob: da5bc18957789f333dbebc22884a0afc54ebd609 (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
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
/** @file
  Arm SCMI Info Library.

  Copyright (c) 2022 - 2023, Arm Limited. All rights reserved.<BR>

  Arm Functional Fixed Hardware Specification:
  - https://developer.arm.com/documentation/den0048/latest/

  SPDX-License-Identifier: BSD-2-Clause-Patent
**/

#include <Library/AcpiLib.h>
#include <Library/DynamicTablesScmiInfoLib.h>
#include <Library/DebugLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Protocol/ArmScmi.h>
#include <Protocol/ArmScmiPerformanceProtocol.h>

/** Arm FFH registers

  Cf. Arm Functional Fixed Hardware Specification
  s3.2 Performance management and Collaborative Processor Performance Control
*/
#define ARM_FFH_DELIVERED_PERF_COUNTER_REGISTER  0x0
#define ARM_FFH_REFERENCE_PERF_COUNTER_REGISTER  0x1

/// Arm SCMI performance protocol.
STATIC SCMI_PERFORMANCE_PROTOCOL  *ScmiPerfProtocol;

/** Arm SCMI Info Library constructor.

  @param  ImageHandle   Image of the loaded driver.
  @param  SystemTable   Pointer to the System Table.

  @retval EFI_SUCCESS             Success.
  @retval EFI_DEVICE_ERROR        Device error.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
  @retval EFI_NOT_FOUND           Not Found
  @retval EFI_TIMEOUT             Timeout.
  @retval EFI_UNSUPPORTED         Unsupported.
**/
EFI_STATUS
EFIAPI
DynamicTablesScmiInfoLibConstructor (
  IN  EFI_HANDLE        ImageHandle,
  IN  EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS  Status;
  UINT32      Version;

  Status = gBS->LocateProtocol (
                  &gArmScmiPerformanceProtocolGuid,
                  NULL,
                  (VOID **)&ScmiPerfProtocol
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = ScmiPerfProtocol->GetVersion (ScmiPerfProtocol, &Version);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  // FastChannels were added in SCMI v2.0 spec.
  if (Version < PERFORMANCE_PROTOCOL_VERSION_V2) {
    DEBUG ((
      DEBUG_ERROR,
      "DynamicTablesScmiInfoLib requires SCMI version > 2.0\n"
      ));
    return EFI_UNSUPPORTED;
  }

  return Status;
}

/** Get the OPPs/performance states of a power domain.

  This function is a wrapper around the SCMI PERFORMANCE_DESCRIBE_LEVELS
  command. The list of discrete performance states is returned in a buffer
  that must be freed by the caller.

  @param[in]  DomainId        Identifier for the performance domain.
  @param[out] LevelArray      If success, pointer to the list of list of
                              performance state. This memory must be freed by
                              the caller.
  @param[out] LevelArrayCount If success, contains the number of states in
                              LevelArray.

  @retval EFI_SUCCESS             Success.
  @retval EFI_DEVICE_ERROR        Device error.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
  @retval EFI_TIMEOUT             Time out.
  @retval EFI_UNSUPPORTED         Unsupported.
**/
STATIC
EFI_STATUS
EFIAPI
DynamicTablesScmiInfoDescribeLevels (
  IN  UINT32                  DomainId,
  OUT SCMI_PERFORMANCE_LEVEL  **LevelArray,
  OUT UINT32                  *LevelArrayCount
  )
{
  EFI_STATUS              Status;
  SCMI_PERFORMANCE_LEVEL  *Array;
  UINT32                  Count;
  UINT32                  Size;

  if ((ScmiPerfProtocol == NULL)  ||
      (LevelArray == NULL)  ||
      (LevelArrayCount == NULL))
  {
    return EFI_INVALID_PARAMETER;
  }

  // First call to get the number of levels.
  Size   = 0;
  Status = ScmiPerfProtocol->DescribeLevels (
                               ScmiPerfProtocol,
                               DomainId,
                               &Count,
                               &Size,
                               NULL
                               );
  if (Status != EFI_BUFFER_TOO_SMALL) {
    // EFI_SUCCESS is not a valid option.
    if (Status == EFI_SUCCESS) {
      return EFI_INVALID_PARAMETER;
    } else {
      return Status;
    }
  }

  Array = AllocateZeroPool (Size);
  if (Array == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  // Second call to get the descriptions of the levels.
  Status = ScmiPerfProtocol->DescribeLevels (
                               ScmiPerfProtocol,
                               DomainId,
                               &Count,
                               &Size,
                               Array
                               );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  *LevelArray      = Array;
  *LevelArrayCount = Count;

  return Status;
}

/** Populate a AML_CPC_INFO object based on SCMI information.

  @param[in]  DomainId    Identifier for the performance domain.
  @param[out] CpcInfo     If success, this structure was populated from
                          information queried to the SCP.

  @retval EFI_SUCCESS             Success.
  @retval EFI_DEVICE_ERROR        Device error.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
  @retval EFI_TIMEOUT             Time out.
  @retval EFI_UNSUPPORTED         Unsupported.
**/
EFI_STATUS
EFIAPI
DynamicTablesScmiInfoGetFastChannel (
  IN  UINT32        DomainId,
  OUT AML_CPC_INFO  *CpcInfo
  )
{
  EFI_STATUS                          Status;
  SCMI_PERFORMANCE_FASTCHANNEL        FcLevelGet;
  SCMI_PERFORMANCE_FASTCHANNEL        FcLimitsSet;
  SCMI_PERFORMANCE_DOMAIN_ATTRIBUTES  DomainAttributes;

  SCMI_PERFORMANCE_LEVEL  *LevelArray;
  UINT32                  LevelCount;

  UINT64  FcLevelGetAddr;
  UINT64  FcLimitsMaxSetAddr;
  UINT64  FcLimitsMinSetAddr;

  if ((ScmiPerfProtocol == NULL)  ||
      (CpcInfo == NULL))
  {
    return EFI_INVALID_PARAMETER;
  }

  Status = ScmiPerfProtocol->DescribeFastchannel (
                               ScmiPerfProtocol,
                               DomainId,
                               ScmiMessageIdPerformanceLevelSet,
                               &FcLevelGet
                               );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = ScmiPerfProtocol->DescribeFastchannel (
                               ScmiPerfProtocol,
                               DomainId,
                               ScmiMessageIdPerformanceLimitsSet,
                               &FcLimitsSet
                               );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = ScmiPerfProtocol->GetDomainAttributes (
                               ScmiPerfProtocol,
                               DomainId,
                               &DomainAttributes
                               );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = DynamicTablesScmiInfoDescribeLevels (DomainId, &LevelArray, &LevelCount);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  /* Do some safety checks.
     Only support FastChannels (and not doorbells) as this is
     the only mechanism supported by SCP.
     FcLimits[Get|Set] require 2 UINT32 values (max, then min) and
     FcLimits[Get|Set] require 1 UINT32 value (level).
  */
  if ((FcLevelGet.ChanSize != sizeof (UINT32))  ||
      ((FcLevelGet.Attributes & SCMI_PERF_FC_ATTRIB_HAS_DOORBELL) ==
       SCMI_PERF_FC_ATTRIB_HAS_DOORBELL) ||
      (FcLimitsSet.ChanSize != 2 * sizeof (UINT32)) ||
      ((FcLimitsSet.Attributes & SCMI_PERF_FC_ATTRIB_HAS_DOORBELL) ==
       SCMI_PERF_FC_ATTRIB_HAS_DOORBELL))
  {
    Status = EFI_INVALID_PARAMETER;
    goto exit_handler;
  }

  FcLevelGetAddr = ((UINT64)FcLevelGet.ChanAddrHigh << 32) |
                   FcLevelGet.ChanAddrLow;
  FcLimitsMaxSetAddr = ((UINT64)FcLimitsSet.ChanAddrHigh << 32) |
                       FcLimitsSet.ChanAddrLow;
  FcLimitsMinSetAddr = FcLimitsMaxSetAddr + 0x4;

  CpcInfo->Revision                          = EFI_ACPI_6_5_AML_CPC_REVISION;
  CpcInfo->HighestPerformanceInteger         = LevelArray[LevelCount - 1].Level;
  CpcInfo->NominalPerformanceInteger         = DomainAttributes.SustainedPerfLevel;
  CpcInfo->LowestNonlinearPerformanceInteger = LevelArray[0].Level;
  CpcInfo->LowestPerformanceInteger          = LevelArray[0].Level;

  CpcInfo->DesiredPerformanceRegister.AddressSpaceId    = EFI_ACPI_6_5_SYSTEM_MEMORY;
  CpcInfo->DesiredPerformanceRegister.RegisterBitWidth  = 32;
  CpcInfo->DesiredPerformanceRegister.RegisterBitOffset = 0;
  CpcInfo->DesiredPerformanceRegister.AccessSize        = EFI_ACPI_6_5_DWORD;
  CpcInfo->DesiredPerformanceRegister.Address           = FcLevelGetAddr;

  CpcInfo->MinimumPerformanceRegister.AddressSpaceId    = EFI_ACPI_6_5_SYSTEM_MEMORY;
  CpcInfo->MinimumPerformanceRegister.RegisterBitWidth  = 32;
  CpcInfo->MinimumPerformanceRegister.RegisterBitOffset = 0;
  CpcInfo->MinimumPerformanceRegister.AccessSize        = EFI_ACPI_6_5_DWORD;
  CpcInfo->MinimumPerformanceRegister.Address           = FcLimitsMinSetAddr;

  CpcInfo->MaximumPerformanceRegister.AddressSpaceId    = EFI_ACPI_6_5_SYSTEM_MEMORY;
  CpcInfo->MaximumPerformanceRegister.RegisterBitWidth  = 32;
  CpcInfo->MaximumPerformanceRegister.RegisterBitOffset = 0;
  CpcInfo->MaximumPerformanceRegister.AccessSize        = EFI_ACPI_6_5_DWORD;
  CpcInfo->MaximumPerformanceRegister.Address           = FcLimitsMaxSetAddr;

  CpcInfo->ReferencePerformanceCounterRegister.AddressSpaceId    = EFI_ACPI_6_5_FUNCTIONAL_FIXED_HARDWARE;
  CpcInfo->ReferencePerformanceCounterRegister.RegisterBitWidth  = 0x40;
  CpcInfo->ReferencePerformanceCounterRegister.RegisterBitOffset = 0;
  CpcInfo->ReferencePerformanceCounterRegister.AccessSize        = ARM_FFH_REFERENCE_PERF_COUNTER_REGISTER;
  CpcInfo->ReferencePerformanceCounterRegister.Address           = 0x4;

  CpcInfo->DeliveredPerformanceCounterRegister.AddressSpaceId    = EFI_ACPI_6_5_FUNCTIONAL_FIXED_HARDWARE;
  CpcInfo->DeliveredPerformanceCounterRegister.RegisterBitWidth  = 0x40;
  CpcInfo->DeliveredPerformanceCounterRegister.RegisterBitOffset = 0;
  CpcInfo->DeliveredPerformanceCounterRegister.AccessSize        = ARM_FFH_DELIVERED_PERF_COUNTER_REGISTER;
  CpcInfo->DeliveredPerformanceCounterRegister.Address           = 0x4;

  // SCMI should advertise performance values on a unified scale. So frequency
  // values are not available. LowestFrequencyInteger and
  // NominalFrequencyInteger are populated in the ConfigurationManager.

exit_handler:
  FreePool (LevelArray);
  return Status;
}