1 // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
2 /******************************************************************************
3 *
4 * Copyright(c) 2020 Intel Corporation
5 *
6 *****************************************************************************/
7
8 #include "iwl-drv.h"
9 #include "pnvm.h"
10 #include "iwl-prph.h"
11 #include "iwl-io.h"
12 #include "fw/api/commands.h"
13 #include "fw/api/nvm-reg.h"
14 #include "fw/api/alive.h"
15
16 struct iwl_pnvm_section {
17 __le32 offset;
18 const u8 data[];
19 } __packed;
20
iwl_pnvm_complete_fn(struct iwl_notif_wait_data * notif_wait,struct iwl_rx_packet * pkt,void * data)21 static bool iwl_pnvm_complete_fn(struct iwl_notif_wait_data *notif_wait,
22 struct iwl_rx_packet *pkt, void *data)
23 {
24 struct iwl_trans *trans = (struct iwl_trans *)data;
25 struct iwl_pnvm_init_complete_ntfy *pnvm_ntf = (void *)pkt->data;
26
27 IWL_DEBUG_FW(trans,
28 "PNVM complete notification received with status %d\n",
29 le32_to_cpu(pnvm_ntf->status));
30
31 return true;
32 }
33
iwl_pnvm_handle_section(struct iwl_trans * trans,const u8 * data,size_t len)34 static int iwl_pnvm_handle_section(struct iwl_trans *trans, const u8 *data,
35 size_t len)
36 {
37 struct iwl_ucode_tlv *tlv;
38 u32 sha1 = 0;
39 u16 mac_type = 0, rf_id = 0;
40 u8 *pnvm_data = NULL, *tmp;
41 bool hw_match = false;
42 u32 size = 0;
43 int ret;
44
45 IWL_DEBUG_FW(trans, "Handling PNVM section\n");
46
47 while (len >= sizeof(*tlv)) {
48 u32 tlv_len, tlv_type;
49
50 len -= sizeof(*tlv);
51 tlv = (void *)data;
52
53 tlv_len = le32_to_cpu(tlv->length);
54 tlv_type = le32_to_cpu(tlv->type);
55
56 if (len < tlv_len) {
57 IWL_ERR(trans, "invalid TLV len: %zd/%u\n",
58 len, tlv_len);
59 ret = -EINVAL;
60 goto out;
61 }
62
63 data += sizeof(*tlv);
64
65 switch (tlv_type) {
66 case IWL_UCODE_TLV_PNVM_VERSION:
67 if (tlv_len < sizeof(__le32)) {
68 IWL_DEBUG_FW(trans,
69 "Invalid size for IWL_UCODE_TLV_PNVM_VERSION (expected %zd, got %d)\n",
70 sizeof(__le32), tlv_len);
71 break;
72 }
73
74 sha1 = le32_to_cpup((__le32 *)data);
75
76 IWL_DEBUG_FW(trans,
77 "Got IWL_UCODE_TLV_PNVM_VERSION %0x\n",
78 sha1);
79 break;
80 case IWL_UCODE_TLV_HW_TYPE:
81 if (tlv_len < 2 * sizeof(__le16)) {
82 IWL_DEBUG_FW(trans,
83 "Invalid size for IWL_UCODE_TLV_HW_TYPE (expected %zd, got %d)\n",
84 2 * sizeof(__le16), tlv_len);
85 break;
86 }
87
88 if (hw_match)
89 break;
90
91 mac_type = le16_to_cpup((__le16 *)data);
92 rf_id = le16_to_cpup((__le16 *)(data + sizeof(__le16)));
93
94 IWL_DEBUG_FW(trans,
95 "Got IWL_UCODE_TLV_HW_TYPE mac_type 0x%0x rf_id 0x%0x\n",
96 mac_type, rf_id);
97
98 if (mac_type == CSR_HW_REV_TYPE(trans->hw_rev) &&
99 rf_id == CSR_HW_RFID_TYPE(trans->hw_rf_id))
100 hw_match = true;
101 break;
102 case IWL_UCODE_TLV_SEC_RT: {
103 struct iwl_pnvm_section *section = (void *)data;
104 u32 data_len = tlv_len - sizeof(*section);
105
106 IWL_DEBUG_FW(trans,
107 "Got IWL_UCODE_TLV_SEC_RT len %d\n",
108 tlv_len);
109
110 /* TODO: remove, this is a deprecated separator */
111 if (le32_to_cpup((__le32 *)data) == 0xddddeeee) {
112 IWL_DEBUG_FW(trans, "Ignoring separator.\n");
113 break;
114 }
115
116 IWL_DEBUG_FW(trans, "Adding data (size %d)\n",
117 data_len);
118
119 tmp = krealloc(pnvm_data, size + data_len, GFP_KERNEL);
120 if (!tmp) {
121 IWL_DEBUG_FW(trans,
122 "Couldn't allocate (more) pnvm_data\n");
123
124 ret = -ENOMEM;
125 goto out;
126 }
127
128 pnvm_data = tmp;
129
130 memcpy(pnvm_data + size, section->data, data_len);
131
132 size += data_len;
133
134 break;
135 }
136 case IWL_UCODE_TLV_PNVM_SKU:
137 IWL_DEBUG_FW(trans,
138 "New PNVM section started, stop parsing.\n");
139 goto done;
140 default:
141 IWL_DEBUG_FW(trans, "Found TLV 0x%0x, len %d\n",
142 tlv_type, tlv_len);
143 break;
144 }
145
146 len -= ALIGN(tlv_len, 4);
147 data += ALIGN(tlv_len, 4);
148 }
149
150 done:
151 if (!hw_match) {
152 IWL_DEBUG_FW(trans,
153 "HW mismatch, skipping PNVM section (need mac_type 0x%x rf_id 0x%x)\n",
154 CSR_HW_REV_TYPE(trans->hw_rev),
155 CSR_HW_RFID_TYPE(trans->hw_rf_id));
156 ret = -ENOENT;
157 goto out;
158 }
159
160 if (!size) {
161 IWL_DEBUG_FW(trans, "Empty PNVM, skipping.\n");
162 ret = -ENOENT;
163 goto out;
164 }
165
166 IWL_INFO(trans, "loaded PNVM version 0x%0x\n", sha1);
167
168 ret = iwl_trans_set_pnvm(trans, pnvm_data, size);
169 out:
170 kfree(pnvm_data);
171 return ret;
172 }
173
iwl_pnvm_parse(struct iwl_trans * trans,const u8 * data,size_t len)174 static int iwl_pnvm_parse(struct iwl_trans *trans, const u8 *data,
175 size_t len)
176 {
177 struct iwl_ucode_tlv *tlv;
178
179 IWL_DEBUG_FW(trans, "Parsing PNVM file\n");
180
181 while (len >= sizeof(*tlv)) {
182 u32 tlv_len, tlv_type;
183
184 len -= sizeof(*tlv);
185 tlv = (void *)data;
186
187 tlv_len = le32_to_cpu(tlv->length);
188 tlv_type = le32_to_cpu(tlv->type);
189
190 if (len < tlv_len) {
191 IWL_ERR(trans, "invalid TLV len: %zd/%u\n",
192 len, tlv_len);
193 return -EINVAL;
194 }
195
196 if (tlv_type == IWL_UCODE_TLV_PNVM_SKU) {
197 struct iwl_sku_id *sku_id =
198 (void *)(data + sizeof(*tlv));
199
200 IWL_DEBUG_FW(trans,
201 "Got IWL_UCODE_TLV_PNVM_SKU len %d\n",
202 tlv_len);
203 IWL_DEBUG_FW(trans, "sku_id 0x%0x 0x%0x 0x%0x\n",
204 le32_to_cpu(sku_id->data[0]),
205 le32_to_cpu(sku_id->data[1]),
206 le32_to_cpu(sku_id->data[2]));
207
208 data += sizeof(*tlv) + ALIGN(tlv_len, 4);
209 len -= ALIGN(tlv_len, 4);
210
211 if (trans->sku_id[0] == le32_to_cpu(sku_id->data[0]) &&
212 trans->sku_id[1] == le32_to_cpu(sku_id->data[1]) &&
213 trans->sku_id[2] == le32_to_cpu(sku_id->data[2])) {
214 int ret;
215
216 ret = iwl_pnvm_handle_section(trans, data, len);
217 if (!ret)
218 return 0;
219 } else {
220 IWL_DEBUG_FW(trans, "SKU ID didn't match!\n");
221 }
222 } else {
223 data += sizeof(*tlv) + ALIGN(tlv_len, 4);
224 len -= ALIGN(tlv_len, 4);
225 }
226 }
227
228 return -ENOENT;
229 }
230
iwl_pnvm_load(struct iwl_trans * trans,struct iwl_notif_wait_data * notif_wait)231 int iwl_pnvm_load(struct iwl_trans *trans,
232 struct iwl_notif_wait_data *notif_wait)
233 {
234 struct iwl_notification_wait pnvm_wait;
235 static const u16 ntf_cmds[] = { WIDE_ID(REGULATORY_AND_NVM_GROUP,
236 PNVM_INIT_COMPLETE_NTFY) };
237 int ret;
238
239 /* if the SKU_ID is empty, there's nothing to do */
240 if (!trans->sku_id[0] && !trans->sku_id[1] && !trans->sku_id[2])
241 return 0;
242
243 /* load from disk only if we haven't done it (or tried) before */
244 if (!trans->pnvm_loaded) {
245 const struct firmware *pnvm;
246 char pnvm_name[64];
247
248 /*
249 * The prefix unfortunately includes a hyphen at the end, so
250 * don't add the dot here...
251 */
252 snprintf(pnvm_name, sizeof(pnvm_name), "%spnvm",
253 trans->cfg->fw_name_pre);
254
255 /* ...but replace the hyphen with the dot here. */
256 if (strlen(trans->cfg->fw_name_pre) < sizeof(pnvm_name))
257 pnvm_name[strlen(trans->cfg->fw_name_pre) - 1] = '.';
258
259 ret = firmware_request_nowarn(&pnvm, pnvm_name, trans->dev);
260 if (ret) {
261 IWL_DEBUG_FW(trans, "PNVM file %s not found %d\n",
262 pnvm_name, ret);
263 /*
264 * Pretend we've loaded it - at least we've tried and
265 * couldn't load it at all, so there's no point in
266 * trying again over and over.
267 */
268 trans->pnvm_loaded = true;
269 } else {
270 iwl_pnvm_parse(trans, pnvm->data, pnvm->size);
271
272 release_firmware(pnvm);
273 }
274 } else {
275 /* if we already loaded, we need to set it again */
276 ret = iwl_trans_set_pnvm(trans, NULL, 0);
277 if (ret)
278 return ret;
279 }
280
281 iwl_init_notification_wait(notif_wait, &pnvm_wait,
282 ntf_cmds, ARRAY_SIZE(ntf_cmds),
283 iwl_pnvm_complete_fn, trans);
284
285 /* kick the doorbell */
286 iwl_write_umac_prph(trans, UREG_DOORBELL_TO_ISR6,
287 UREG_DOORBELL_TO_ISR6_PNVM);
288
289 return iwl_wait_notification(notif_wait, &pnvm_wait,
290 MVM_UCODE_PNVM_TIMEOUT);
291 }
292 IWL_EXPORT_SYMBOL(iwl_pnvm_load);
293