summaryrefslogtreecommitdiffstats
path: root/OvmfPkg/Library/UefiPciCapPciIoLib/UefiPciCapPciIoLib.c
blob: 2b6835626fe408913cd653a23dabed3357d5d8cb (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
/** @file
  Plug an EFI_PCI_IO_PROTOCOL backend into PciCapLib, for config space access.

  Copyright (C) 2018, Red Hat, Inc.

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

#include <Library/MemoryAllocationLib.h>

#include "UefiPciCapPciIoLib.h"


/**
  Transfer bytes between the config space of a given PCI device and a memory
  buffer.

  ProtoDevTransferConfig() performs as few config space accesses as possible
  (without attempting 64-bit wide accesses).

  @param[in] PciIo             The EFI_PCI_IO_PROTOCOL representation of the
                               PCI device.

  @param[in] TransferFunction  The EFI_PCI_IO_PROTOCOL_CONFIG function that
                               implements the transfer. The direction of the
                               transfer is inherent to TransferFunction.
                               TransferFunction() is required to return an
                               unspecified error if any sub-transfer within
                               Size bytes from ConfigOffset exceeds the config
                               space limit of the PCI device.

  @param[in] ConfigOffset      The offset in the config space of the PCI device
                               at which the transfer should commence.

  @param[in,out] Buffer        The memory buffer where the transfer should
                               occur.

  @param[in] Size              The number of bytes to transfer.

  @retval EFI_SUCCESS  Size bytes have been transferred between config space
                       and Buffer.

  @return              Error codes propagated from TransferFunction(). Fewer
                       than Size bytes may have been transferred.
**/
STATIC
EFI_STATUS
ProtoDevTransferConfig (
  IN     EFI_PCI_IO_PROTOCOL        *PciIo,
  IN     EFI_PCI_IO_PROTOCOL_CONFIG TransferFunction,
  IN     UINT16                     ConfigOffset,
  IN OUT UINT8                      *Buffer,
  IN     UINT16                     Size
  )
{
  while (Size > 0) {
    EFI_PCI_IO_PROTOCOL_WIDTH Width;
    UINT16                    Count;
    EFI_STATUS                Status;
    UINT16                    Progress;

    //
    // Pick the largest access size that is allowed by the remaining transfer
    // Size and by the alignment of ConfigOffset.
    //
    // When the largest access size is available, transfer as many bytes as
    // possible in one iteration of the loop. Otherwise, transfer only one
    // unit, to improve the alignment.
    //
    if (Size >= 4 && (ConfigOffset & 3) == 0) {
      Width = EfiPciIoWidthUint32;
      Count = Size >> Width;
    } else if (Size >= 2 && (ConfigOffset & 1) == 0) {
      Width = EfiPciIoWidthUint16;
      Count = 1;
    } else {
      Width = EfiPciIoWidthUint8;
      Count = 1;
    }
    Status = TransferFunction (PciIo, Width, ConfigOffset, Count, Buffer);
    if (EFI_ERROR (Status)) {
      return Status;
    }
    Progress      = Count << Width;
    ConfigOffset += Progress;
    Buffer       += Progress;
    Size         -= Progress;
  }
  return EFI_SUCCESS;
}


/**
  Read the config space of a given PCI device (both normal and extended).

  ProtoDevReadConfig() performs as few config space accesses as possible
  (without attempting 64-bit wide accesses).

  ProtoDevReadConfig() returns an unspecified error if accessing Size bytes
  from SourceOffset exceeds the config space limit of the PCI device. Fewer
  than Size bytes may have been read in this case.

  @param[in] PciDevice           Implementation-specific unique representation
                                 of the PCI device in the PCI hierarchy.

  @param[in] SourceOffset        Source offset in the config space of the PCI
                                 device to start reading from.

  @param[out] DestinationBuffer  Buffer to store the read data to.

  @param[in] Size                The number of bytes to transfer.

  @retval RETURN_SUCCESS  Size bytes have been transferred from config space to
                          DestinationBuffer.

  @return                 Error codes propagated from
                          EFI_PCI_IO_PROTOCOL.Pci.Read(). Fewer than Size bytes
                          may have been read.
**/
STATIC
RETURN_STATUS
EFIAPI
ProtoDevReadConfig (
  IN  PCI_CAP_DEV *PciDevice,
  IN  UINT16      SourceOffset,
  OUT VOID        *DestinationBuffer,
  IN  UINT16      Size
  )
{
  PROTO_DEV *ProtoDev;

  ProtoDev = PROTO_DEV_FROM_PCI_CAP_DEV (PciDevice);
  return ProtoDevTransferConfig (ProtoDev->PciIo, ProtoDev->PciIo->Pci.Read,
           SourceOffset, DestinationBuffer, Size);
}


/**
  Write the config space of a given PCI device (both normal and extended).

  ProtoDevWriteConfig() performs as few config space accesses as possible
  (without attempting 64-bit wide accesses).

  ProtoDevWriteConfig() returns an unspecified error if accessing Size bytes at
  DestinationOffset exceeds the config space limit of the PCI device. Fewer
  than Size bytes may have been written in this case.

  @param[in] PciDevice          Implementation-specific unique representation
                                of the PCI device in the PCI hierarchy.

  @param[in] DestinationOffset  Destination offset in the config space of the
                                PCI device to start writing at.

  @param[in] SourceBuffer       Buffer to read the data to be stored from.

  @param[in] Size               The number of bytes to transfer.

  @retval RETURN_SUCCESS  Size bytes have been transferred from SourceBuffer to
                          config space.

  @return                 Error codes propagated from
                          EFI_PCI_IO_PROTOCOL.Pci.Write(). Fewer than Size
                          bytes may have been written.
**/
STATIC
RETURN_STATUS
EFIAPI
ProtoDevWriteConfig (
  IN PCI_CAP_DEV *PciDevice,
  IN UINT16      DestinationOffset,
  IN VOID        *SourceBuffer,
  IN UINT16      Size
  )
{
  PROTO_DEV *ProtoDev;

  ProtoDev = PROTO_DEV_FROM_PCI_CAP_DEV (PciDevice);
  return ProtoDevTransferConfig (ProtoDev->PciIo, ProtoDev->PciIo->Pci.Write,
           DestinationOffset, SourceBuffer, Size);
}


/**
  Create a PCI_CAP_DEV object from an EFI_PCI_IO_PROTOCOL instance. The config
  space accessors are based upon EFI_PCI_IO_PROTOCOL.Pci.Read() and
  EFI_PCI_IO_PROTOCOL.Pci.Write().

  @param[in] PciIo       EFI_PCI_IO_PROTOCOL representation of the PCI device.

  @param[out] PciDevice  The PCI_CAP_DEV object constructed as described above.
                         PciDevice can be passed to the PciCapLib APIs.

  @retval EFI_SUCCESS           PciDevice has been constructed and output.

  @retval EFI_OUT_OF_RESOURCES  Memory allocation failed.
**/
EFI_STATUS
EFIAPI
PciCapPciIoDeviceInit (
  IN  EFI_PCI_IO_PROTOCOL *PciIo,
  OUT PCI_CAP_DEV         **PciDevice
  )
{
  PROTO_DEV *ProtoDev;

  ProtoDev = AllocatePool (sizeof *ProtoDev);
  if (ProtoDev == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  ProtoDev->Signature              = PROTO_DEV_SIG;
  ProtoDev->PciIo                  = PciIo;
  ProtoDev->BaseDevice.ReadConfig  = ProtoDevReadConfig;
  ProtoDev->BaseDevice.WriteConfig = ProtoDevWriteConfig;

  *PciDevice = &ProtoDev->BaseDevice;
  return EFI_SUCCESS;
}


/**
  Free the resources used by PciDevice.

  @param[in] PciDevice  The PCI_CAP_DEV object to free, originally produced by
                        PciCapPciIoDeviceInit().
**/
VOID
EFIAPI
PciCapPciIoDeviceUninit (
  IN PCI_CAP_DEV *PciDevice
  )
{
  PROTO_DEV *ProtoDev;

  ProtoDev = PROTO_DEV_FROM_PCI_CAP_DEV (PciDevice);
  FreePool (ProtoDev);
}