1 /** @file
2   HddPassword PEI module which is used to unlock HDD password for S3.
3 
4   Copyright (c) 2019, Intel Corporation. All rights reserved.<BR>
5 
6   SPDX-License-Identifier: BSD-2-Clause-Patent
7 
8 **/
9 
10 #include "HddPasswordPei.h"
11 
12 EFI_GUID mHddPasswordDeviceInfoGuid = HDD_PASSWORD_DEVICE_INFO_GUID;
13 
14 
15 /**
16   Send unlock hdd password cmd through ATA PassThru PPI.
17 
18   @param[in] AtaPassThru           The pointer to the ATA PassThru PPI.
19   @param[in] Port                  The port number of the ATA device.
20   @param[in] PortMultiplierPort    The port multiplier port number of the ATA device.
21   @param[in] Identifier            The identifier to set user or master password.
22   @param[in] Password              The hdd password of attached ATA device.
23 
24   @retval EFI_SUCCESS              Successful to send unlock hdd password cmd.
25   @retval EFI_INVALID_PARAMETER    The parameter passed-in is invalid.
26   @retval EFI_OUT_OF_RESOURCES     Not enough memory to send unlock hdd password cmd.
27   @retval EFI_DEVICE_ERROR         Can not send unlock hdd password cmd.
28 
29 **/
30 EFI_STATUS
UnlockDevice(IN EDKII_PEI_ATA_PASS_THRU_PPI * AtaPassThru,IN UINT16 Port,IN UINT16 PortMultiplierPort,IN CHAR8 Identifier,IN CHAR8 * Password)31 UnlockDevice (
32   IN EDKII_PEI_ATA_PASS_THRU_PPI    *AtaPassThru,
33   IN UINT16                         Port,
34   IN UINT16                         PortMultiplierPort,
35   IN CHAR8                          Identifier,
36   IN CHAR8                          *Password
37   )
38 {
39   EFI_STATUS                          Status;
40   EFI_ATA_COMMAND_BLOCK               Acb;
41   EFI_ATA_STATUS_BLOCK                *Asb;
42   EFI_ATA_PASS_THRU_COMMAND_PACKET    Packet;
43   UINT8                               Buffer[HDD_PAYLOAD];
44 
45   if ((AtaPassThru == NULL) || (Password == NULL)) {
46     return EFI_INVALID_PARAMETER;
47   }
48 
49   //
50   // The 'Asb' field (a pointer to the EFI_ATA_STATUS_BLOCK structure) in
51   // EFI_ATA_PASS_THRU_COMMAND_PACKET is required to be aligned specified by
52   // the 'IoAlign' field in the EFI_ATA_PASS_THRU_MODE structure. Meanwhile,
53   // the structure EFI_ATA_STATUS_BLOCK is composed of only UINT8 fields, so it
54   // may not be aligned when allocated on stack for some compilers. Hence, we
55   // use the API AllocateAlignedPages to ensure this structure is properly
56   // aligned.
57   //
58   Asb = AllocateAlignedPages (
59           EFI_SIZE_TO_PAGES (sizeof (EFI_ATA_STATUS_BLOCK)),
60           AtaPassThru->Mode->IoAlign
61           );
62   if (Asb == NULL) {
63     return EFI_OUT_OF_RESOURCES;
64   }
65 
66   //
67   // Prepare for ATA command block.
68   //
69   ZeroMem (&Acb, sizeof (Acb));
70   ZeroMem (Asb, sizeof (EFI_ATA_STATUS_BLOCK));
71   Acb.AtaCommand    = ATA_SECURITY_UNLOCK_CMD;
72   Acb.AtaDeviceHead = (UINT8) (PortMultiplierPort == 0xFFFF ? 0 : (PortMultiplierPort << 4));
73 
74   //
75   // Prepare for ATA pass through packet.
76   //
77   ZeroMem (&Packet, sizeof (Packet));
78   Packet.Protocol = EFI_ATA_PASS_THRU_PROTOCOL_PIO_DATA_OUT;
79   Packet.Length   = EFI_ATA_PASS_THRU_LENGTH_BYTES;
80   Packet.Asb      = Asb;
81   Packet.Acb      = &Acb;
82 
83   ((CHAR16 *) Buffer)[0] = Identifier & BIT0;
84   CopyMem (&((CHAR16 *) Buffer)[1], Password, HDD_PASSWORD_MAX_LENGTH);
85 
86   Packet.OutDataBuffer     = Buffer;
87   Packet.OutTransferLength = sizeof (Buffer);
88   Packet.Timeout           = ATA_TIMEOUT;
89 
90   Status = AtaPassThru->PassThru (
91                           AtaPassThru,
92                           Port,
93                           PortMultiplierPort,
94                           &Packet
95                           );
96   if (!EFI_ERROR (Status) &&
97       ((Asb->AtaStatus & ATA_STSREG_ERR) != 0) &&
98       ((Asb->AtaError & ATA_ERRREG_ABRT) != 0)) {
99     Status = EFI_DEVICE_ERROR;
100   }
101 
102   FreeAlignedPages (Asb, EFI_SIZE_TO_PAGES (sizeof (EFI_ATA_STATUS_BLOCK)));
103 
104   ZeroMem (Buffer, sizeof (Buffer));
105 
106   DEBUG ((DEBUG_INFO, "%a() - %r\n", __FUNCTION__, Status));
107   return Status;
108 }
109 
110 /**
111   Send security freeze lock cmd through ATA PassThru PPI.
112 
113   @param[in] AtaPassThru           The pointer to the ATA PassThru PPI.
114   @param[in] Port                  The port number of the ATA device.
115   @param[in] PortMultiplierPort    The port multiplier port number of the ATA device.
116 
117   @retval EFI_SUCCESS              Successful to send security freeze lock cmd.
118   @retval EFI_INVALID_PARAMETER    The parameter passed-in is invalid.
119   @retval EFI_OUT_OF_RESOURCES     Not enough memory to send unlock hdd password cmd.
120   @retval EFI_DEVICE_ERROR         Can not send security freeze lock cmd.
121 
122 **/
123 EFI_STATUS
FreezeLockDevice(IN EDKII_PEI_ATA_PASS_THRU_PPI * AtaPassThru,IN UINT16 Port,IN UINT16 PortMultiplierPort)124 FreezeLockDevice (
125   IN EDKII_PEI_ATA_PASS_THRU_PPI    *AtaPassThru,
126   IN UINT16                         Port,
127   IN UINT16                         PortMultiplierPort
128   )
129 {
130   EFI_STATUS                          Status;
131   EFI_ATA_COMMAND_BLOCK               Acb;
132   EFI_ATA_STATUS_BLOCK                *Asb;
133   EFI_ATA_PASS_THRU_COMMAND_PACKET    Packet;
134 
135   if (AtaPassThru == NULL) {
136     return EFI_INVALID_PARAMETER;
137   }
138 
139   //
140   // The 'Asb' field (a pointer to the EFI_ATA_STATUS_BLOCK structure) in
141   // EFI_ATA_PASS_THRU_COMMAND_PACKET is required to be aligned specified by
142   // the 'IoAlign' field in the EFI_ATA_PASS_THRU_MODE structure. Meanwhile,
143   // the structure EFI_ATA_STATUS_BLOCK is composed of only UINT8 fields, so it
144   // may not be aligned when allocated on stack for some compilers. Hence, we
145   // use the API AllocateAlignedPages to ensure this structure is properly
146   // aligned.
147   //
148   Asb = AllocateAlignedPages (
149           EFI_SIZE_TO_PAGES (sizeof (EFI_ATA_STATUS_BLOCK)),
150           AtaPassThru->Mode->IoAlign
151           );
152   if (Asb == NULL) {
153     return EFI_OUT_OF_RESOURCES;
154   }
155 
156   //
157   // Prepare for ATA command block.
158   //
159   ZeroMem (&Acb, sizeof (Acb));
160   ZeroMem (Asb, sizeof (EFI_ATA_STATUS_BLOCK));
161   Acb.AtaCommand    = ATA_SECURITY_FREEZE_LOCK_CMD;
162   Acb.AtaDeviceHead = (UINT8) (PortMultiplierPort == 0xFFFF ? 0 : (PortMultiplierPort << 4));
163 
164   //
165   // Prepare for ATA pass through packet.
166   //
167   ZeroMem (&Packet, sizeof (Packet));
168   Packet.Protocol = EFI_ATA_PASS_THRU_PROTOCOL_ATA_NON_DATA;
169   Packet.Length   = EFI_ATA_PASS_THRU_LENGTH_NO_DATA_TRANSFER;
170   Packet.Asb      = Asb;
171   Packet.Acb      = &Acb;
172   Packet.Timeout  = ATA_TIMEOUT;
173 
174   Status = AtaPassThru->PassThru (
175                           AtaPassThru,
176                           Port,
177                           PortMultiplierPort,
178                           &Packet
179                           );
180   if (!EFI_ERROR (Status) &&
181       ((Asb->AtaStatus & ATA_STSREG_ERR) != 0) &&
182       ((Asb->AtaError & ATA_ERRREG_ABRT) != 0)) {
183     Status = EFI_DEVICE_ERROR;
184   }
185 
186   FreeAlignedPages (Asb, EFI_SIZE_TO_PAGES (sizeof (EFI_ATA_STATUS_BLOCK)));
187 
188   DEBUG ((DEBUG_INFO, "%a() - %r\n", __FUNCTION__, Status));
189   return Status;
190 }
191 
192 /**
193   Unlock HDD password for S3.
194 
195   @param[in] AtaPassThruPpi    Pointer to the EDKII_PEI_ATA_PASS_THRU_PPI instance.
196 
197 **/
198 VOID
UnlockHddPassword(IN EDKII_PEI_ATA_PASS_THRU_PPI * AtaPassThruPpi)199 UnlockHddPassword (
200   IN EDKII_PEI_ATA_PASS_THRU_PPI    *AtaPassThruPpi
201   )
202 {
203   EFI_STATUS                     Status;
204   VOID                           *Buffer;
205   UINTN                          Length;
206   UINT8                          DummyData;
207   HDD_PASSWORD_DEVICE_INFO       *DevInfo;
208   UINT16                         Port;
209   UINT16                         PortMultiplierPort;
210   EFI_DEVICE_PATH_PROTOCOL       *DevicePath;
211   UINTN                          DevicePathLength;
212 
213   //
214   // Get HDD password device info from LockBox.
215   //
216   Buffer = (VOID *) &DummyData;
217   Length = sizeof (DummyData);
218   Status = RestoreLockBox (&mHddPasswordDeviceInfoGuid, Buffer, &Length);
219   if (Status == EFI_BUFFER_TOO_SMALL) {
220     Buffer = AllocatePages (EFI_SIZE_TO_PAGES (Length));
221     if (Buffer != NULL) {
222       Status = RestoreLockBox (&mHddPasswordDeviceInfoGuid, Buffer, &Length);
223     }
224   }
225   if ((Buffer == NULL) || (Buffer == (VOID *) &DummyData)) {
226     return;
227   } else if (EFI_ERROR (Status)) {
228     FreePages (Buffer, EFI_SIZE_TO_PAGES (Length));
229     return;
230   }
231 
232   Status = AtaPassThruPpi->GetDevicePath (AtaPassThruPpi, &DevicePathLength, &DevicePath);
233   if (EFI_ERROR (Status) || (DevicePathLength <= sizeof (EFI_DEVICE_PATH_PROTOCOL))) {
234     goto Exit;
235   }
236 
237   //
238   // Go through all the devices managed by the AtaPassThru PPI instance.
239   //
240   Port = 0xFFFF;
241   while (TRUE) {
242     Status = AtaPassThruPpi->GetNextPort (AtaPassThruPpi, &Port);
243     if (EFI_ERROR (Status)) {
244       //
245       // We cannot find more legal port then we are done.
246       //
247       break;
248     }
249 
250     PortMultiplierPort = 0xFFFF;
251     while (TRUE) {
252       Status = AtaPassThruPpi->GetNextDevice (AtaPassThruPpi, Port, &PortMultiplierPort);
253       if (EFI_ERROR (Status)) {
254         //
255         // We cannot find more legal port multiplier port number for ATA device
256         // on the port, then we are done.
257         //
258         break;
259       }
260 
261       //
262       // Search the device in the restored LockBox.
263       //
264       DevInfo = (HDD_PASSWORD_DEVICE_INFO *) Buffer;
265       while ((UINTN) DevInfo < ((UINTN) Buffer + Length)) {
266         //
267         // Find the matching device.
268         //
269         if ((DevInfo->Device.Port == Port) &&
270             (DevInfo->Device.PortMultiplierPort == PortMultiplierPort) &&
271             (DevInfo->DevicePathLength >= DevicePathLength) &&
272             (CompareMem (
273               DevInfo->DevicePath,
274               DevicePath,
275               DevicePathLength - sizeof (EFI_DEVICE_PATH_PROTOCOL)) == 0)) {
276           //
277           // If device locked, unlock first.
278           //
279           if (!IsZeroBuffer (DevInfo->Password, HDD_PASSWORD_MAX_LENGTH)) {
280             UnlockDevice (AtaPassThruPpi, Port, PortMultiplierPort, 0, DevInfo->Password);
281           }
282           //
283           // Freeze lock the device.
284           //
285           FreezeLockDevice (AtaPassThruPpi, Port, PortMultiplierPort);
286           break;
287         }
288 
289         DevInfo = (HDD_PASSWORD_DEVICE_INFO *)
290                   ((UINTN) DevInfo + sizeof (HDD_PASSWORD_DEVICE_INFO) + DevInfo->DevicePathLength);
291       }
292     }
293   }
294 
295 Exit:
296   ZeroMem (Buffer, Length);
297   FreePages (Buffer, EFI_SIZE_TO_PAGES (Length));
298 
299 }
300 
301 /**
302   Entry point of the notification callback function itself within the PEIM.
303   It is to unlock HDD password for S3.
304 
305   @param  PeiServices      Indirect reference to the PEI Services Table.
306   @param  NotifyDescriptor Address of the notification descriptor data structure.
307   @param  Ppi              Address of the PPI that was installed.
308 
309   @return Status of the notification.
310           The status code returned from this function is ignored.
311 **/
312 EFI_STATUS
313 EFIAPI
HddPasswordAtaPassThruNotify(IN EFI_PEI_SERVICES ** PeiServices,IN EFI_PEI_NOTIFY_DESCRIPTOR * NotifyDesc,IN VOID * Ppi)314 HddPasswordAtaPassThruNotify (
315   IN EFI_PEI_SERVICES          **PeiServices,
316   IN EFI_PEI_NOTIFY_DESCRIPTOR *NotifyDesc,
317   IN VOID                      *Ppi
318   )
319 {
320   DEBUG ((DEBUG_INFO, "%a() - enter at S3 resume\n", __FUNCTION__));
321 
322   UnlockHddPassword ((EDKII_PEI_ATA_PASS_THRU_PPI *) Ppi);
323 
324   DEBUG ((DEBUG_INFO, "%a() - exit at S3 resume\n", __FUNCTION__));
325 
326   return EFI_SUCCESS;
327 }
328 
329 
330 EFI_PEI_NOTIFY_DESCRIPTOR mHddPasswordAtaPassThruPpiNotifyDesc = {
331   (EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST),
332   &gEdkiiPeiAtaPassThruPpiGuid,
333   HddPasswordAtaPassThruNotify
334 };
335 
336 
337 /**
338   Main entry for this module.
339 
340   @param FileHandle             Handle of the file being invoked.
341   @param PeiServices            Pointer to PEI Services table.
342 
343   @return Status from PeiServicesNotifyPpi.
344 
345 **/
346 EFI_STATUS
347 EFIAPI
HddPasswordPeiInit(IN EFI_PEI_FILE_HANDLE FileHandle,IN CONST EFI_PEI_SERVICES ** PeiServices)348 HddPasswordPeiInit (
349   IN EFI_PEI_FILE_HANDLE        FileHandle,
350   IN CONST EFI_PEI_SERVICES     **PeiServices
351   )
352 {
353   EFI_STATUS                              Status;
354   EFI_BOOT_MODE                           BootMode;
355 
356   Status = PeiServicesGetBootMode (&BootMode);
357   if ((EFI_ERROR (Status)) || (BootMode != BOOT_ON_S3_RESUME)) {
358     return EFI_UNSUPPORTED;
359   }
360 
361   DEBUG ((DEBUG_INFO, "%a: Enters in S3 path.\n", __FUNCTION__));
362 
363   Status = PeiServicesNotifyPpi (&mHddPasswordAtaPassThruPpiNotifyDesc);
364   ASSERT_EFI_ERROR (Status);
365   return Status;
366 }
367 
368