Merge tag 'mac80211-next-for-net-next-2021-06-25' of git://git.kernel.org/pub/scm...
[linux-2.6-microblaze.git] / drivers / net / wireless / intel / iwlwifi / fw / uefi.c
1 // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
2 /*
3  * Copyright(c) 2021 Intel Corporation
4  */
5
6 #include "iwl-drv.h"
7 #include "pnvm.h"
8 #include "iwl-prph.h"
9 #include "iwl-io.h"
10
11 #include "fw/uefi.h"
12 #include "fw/api/alive.h"
13 #include <linux/efi.h>
14
15 #define IWL_EFI_VAR_GUID EFI_GUID(0x92daaf2f, 0xc02b, 0x455b,   \
16                                   0xb2, 0xec, 0xf5, 0xa3,       \
17                                   0x59, 0x4f, 0x4a, 0xea)
18
19 void *iwl_uefi_get_pnvm(struct iwl_trans *trans, size_t *len)
20 {
21         struct efivar_entry *pnvm_efivar;
22         void *data;
23         unsigned long package_size;
24         int err;
25
26         *len = 0;
27
28         pnvm_efivar = kzalloc(sizeof(*pnvm_efivar), GFP_KERNEL);
29         if (!pnvm_efivar)
30                 return ERR_PTR(-ENOMEM);
31
32         memcpy(&pnvm_efivar->var.VariableName, IWL_UEFI_OEM_PNVM_NAME,
33                sizeof(IWL_UEFI_OEM_PNVM_NAME));
34         pnvm_efivar->var.VendorGuid = IWL_EFI_VAR_GUID;
35
36         /*
37          * TODO: we hardcode a maximum length here, because reading
38          * from the UEFI is not working.  To implement this properly,
39          * we have to call efivar_entry_size().
40          */
41         package_size = IWL_HARDCODED_PNVM_SIZE;
42
43         data = kmalloc(package_size, GFP_KERNEL);
44         if (!data) {
45                 data = ERR_PTR(-ENOMEM);
46                 goto out;
47         }
48
49         err = efivar_entry_get(pnvm_efivar, NULL, &package_size, data);
50         if (err) {
51                 IWL_DEBUG_FW(trans,
52                              "PNVM UEFI variable not found %d (len %zd)\n",
53                              err, package_size);
54                 kfree(data);
55                 data = ERR_PTR(err);
56                 goto out;
57         }
58
59         IWL_DEBUG_FW(trans, "Read PNVM from UEFI with size %zd\n", package_size);
60         *len = package_size;
61
62 out:
63         kfree(pnvm_efivar);
64
65         return data;
66 }
67
68 static void *iwl_uefi_reduce_power_section(struct iwl_trans *trans,
69                                            const u8 *data, size_t len)
70 {
71         struct iwl_ucode_tlv *tlv;
72         u8 *reduce_power_data = NULL, *tmp;
73         u32 size = 0;
74
75         IWL_DEBUG_FW(trans, "Handling REDUCE_POWER section\n");
76
77         while (len >= sizeof(*tlv)) {
78                 u32 tlv_len, tlv_type;
79
80                 len -= sizeof(*tlv);
81                 tlv = (void *)data;
82
83                 tlv_len = le32_to_cpu(tlv->length);
84                 tlv_type = le32_to_cpu(tlv->type);
85
86                 if (len < tlv_len) {
87                         IWL_ERR(trans, "invalid TLV len: %zd/%u\n",
88                                 len, tlv_len);
89                         reduce_power_data = ERR_PTR(-EINVAL);
90                         goto out;
91                 }
92
93                 data += sizeof(*tlv);
94
95                 switch (tlv_type) {
96                 case IWL_UCODE_TLV_MEM_DESC: {
97                         IWL_DEBUG_FW(trans,
98                                      "Got IWL_UCODE_TLV_MEM_DESC len %d\n",
99                                      tlv_len);
100
101                         IWL_DEBUG_FW(trans, "Adding data (size %d)\n", tlv_len);
102
103                         tmp = krealloc(reduce_power_data, size + tlv_len, GFP_KERNEL);
104                         if (!tmp) {
105                                 IWL_DEBUG_FW(trans,
106                                              "Couldn't allocate (more) reduce_power_data\n");
107
108                                 reduce_power_data = ERR_PTR(-ENOMEM);
109                                 goto out;
110                         }
111
112                         reduce_power_data = tmp;
113
114                         memcpy(reduce_power_data + size, data, tlv_len);
115
116                         size += tlv_len;
117
118                         break;
119                 }
120                 case IWL_UCODE_TLV_PNVM_SKU:
121                         IWL_DEBUG_FW(trans,
122                                      "New REDUCE_POWER section started, stop parsing.\n");
123                         goto done;
124                 default:
125                         IWL_DEBUG_FW(trans, "Found TLV 0x%0x, len %d\n",
126                                      tlv_type, tlv_len);
127                         break;
128                 }
129
130                 len -= ALIGN(tlv_len, 4);
131                 data += ALIGN(tlv_len, 4);
132         }
133
134 done:
135         if (!size) {
136                 IWL_DEBUG_FW(trans, "Empty REDUCE_POWER, skipping.\n");
137                 reduce_power_data = ERR_PTR(-ENOENT);
138                 goto out;
139         }
140
141         IWL_INFO(trans, "loaded REDUCE_POWER\n");
142
143 out:
144         return reduce_power_data;
145 }
146
147 static void *iwl_uefi_reduce_power_parse(struct iwl_trans *trans,
148                                          const u8 *data, size_t len)
149 {
150         struct iwl_ucode_tlv *tlv;
151         void *sec_data;
152
153         IWL_DEBUG_FW(trans, "Parsing REDUCE_POWER data\n");
154
155         while (len >= sizeof(*tlv)) {
156                 u32 tlv_len, tlv_type;
157
158                 len -= sizeof(*tlv);
159                 tlv = (void *)data;
160
161                 tlv_len = le32_to_cpu(tlv->length);
162                 tlv_type = le32_to_cpu(tlv->type);
163
164                 if (len < tlv_len) {
165                         IWL_ERR(trans, "invalid TLV len: %zd/%u\n",
166                                 len, tlv_len);
167                         return ERR_PTR(-EINVAL);
168                 }
169
170                 if (tlv_type == IWL_UCODE_TLV_PNVM_SKU) {
171                         struct iwl_sku_id *sku_id =
172                                 (void *)(data + sizeof(*tlv));
173
174                         IWL_DEBUG_FW(trans,
175                                      "Got IWL_UCODE_TLV_PNVM_SKU len %d\n",
176                                      tlv_len);
177                         IWL_DEBUG_FW(trans, "sku_id 0x%0x 0x%0x 0x%0x\n",
178                                      le32_to_cpu(sku_id->data[0]),
179                                      le32_to_cpu(sku_id->data[1]),
180                                      le32_to_cpu(sku_id->data[2]));
181
182                         data += sizeof(*tlv) + ALIGN(tlv_len, 4);
183                         len -= ALIGN(tlv_len, 4);
184
185                         if (trans->sku_id[0] == le32_to_cpu(sku_id->data[0]) &&
186                             trans->sku_id[1] == le32_to_cpu(sku_id->data[1]) &&
187                             trans->sku_id[2] == le32_to_cpu(sku_id->data[2])) {
188                                 sec_data = iwl_uefi_reduce_power_section(trans,
189                                                                          data,
190                                                                          len);
191                                 if (!IS_ERR(sec_data))
192                                         return sec_data;
193                         } else {
194                                 IWL_DEBUG_FW(trans, "SKU ID didn't match!\n");
195                         }
196                 } else {
197                         data += sizeof(*tlv) + ALIGN(tlv_len, 4);
198                         len -= ALIGN(tlv_len, 4);
199                 }
200         }
201
202         return ERR_PTR(-ENOENT);
203 }
204
205 void *iwl_uefi_get_reduced_power(struct iwl_trans *trans, size_t *len)
206 {
207         struct efivar_entry *reduce_power_efivar;
208         struct pnvm_sku_package *package;
209         void *data = NULL;
210         unsigned long package_size;
211         int err;
212
213         *len = 0;
214
215         reduce_power_efivar = kzalloc(sizeof(*reduce_power_efivar), GFP_KERNEL);
216         if (!reduce_power_efivar)
217                 return ERR_PTR(-ENOMEM);
218
219         memcpy(&reduce_power_efivar->var.VariableName, IWL_UEFI_REDUCED_POWER_NAME,
220                sizeof(IWL_UEFI_REDUCED_POWER_NAME));
221         reduce_power_efivar->var.VendorGuid = IWL_EFI_VAR_GUID;
222
223         /*
224          * TODO: we hardcode a maximum length here, because reading
225          * from the UEFI is not working.  To implement this properly,
226          * we have to call efivar_entry_size().
227          */
228         package_size = IWL_HARDCODED_REDUCE_POWER_SIZE;
229
230         package = kmalloc(package_size, GFP_KERNEL);
231         if (!package) {
232                 package = ERR_PTR(-ENOMEM);
233                 goto out;
234         }
235
236         err = efivar_entry_get(reduce_power_efivar, NULL, &package_size, package);
237         if (err) {
238                 IWL_DEBUG_FW(trans,
239                              "Reduced Power UEFI variable not found %d (len %lu)\n",
240                              err, package_size);
241                 kfree(package);
242                 data = ERR_PTR(err);
243                 goto out;
244         }
245
246         IWL_DEBUG_FW(trans, "Read reduced power from UEFI with size %lu\n",
247                      package_size);
248         *len = package_size;
249
250         IWL_DEBUG_FW(trans, "rev %d, total_size %d, n_skus %d\n",
251                      package->rev, package->total_size, package->n_skus);
252
253         data = iwl_uefi_reduce_power_parse(trans, package->data,
254                                            *len - sizeof(*package));
255
256         kfree(package);
257
258 out:
259         kfree(reduce_power_efivar);
260
261         return data;
262 }