Commit | Line | Data |
---|---|---|
8ca151b5 JB |
1 | /****************************************************************************** |
2 | * | |
3 | * This file is provided under a dual BSD/GPLv2 license. When using or | |
4 | * redistributing this file, you may do so under either license. | |
5 | * | |
6 | * GPL LICENSE SUMMARY | |
7 | * | |
8 | * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. | |
9 | * | |
10 | * This program is free software; you can redistribute it and/or modify | |
11 | * it under the terms of version 2 of the GNU General Public License as | |
12 | * published by the Free Software Foundation. | |
13 | * | |
14 | * This program is distributed in the hope that it will be useful, but | |
15 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
17 | * General Public License for more details. | |
18 | * | |
19 | * You should have received a copy of the GNU General Public License | |
20 | * along with this program; if not, write to the Free Software | |
21 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, | |
22 | * USA | |
23 | * | |
24 | * The full GNU General Public License is included in this distribution | |
410dc5aa | 25 | * in the file called COPYING. |
8ca151b5 JB |
26 | * |
27 | * Contact Information: | |
28 | * Intel Linux Wireless <ilw@linux.intel.com> | |
29 | * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 | |
30 | * | |
31 | * BSD LICENSE | |
32 | * | |
33 | * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. | |
34 | * All rights reserved. | |
35 | * | |
36 | * Redistribution and use in source and binary forms, with or without | |
37 | * modification, are permitted provided that the following conditions | |
38 | * are met: | |
39 | * | |
40 | * * Redistributions of source code must retain the above copyright | |
41 | * notice, this list of conditions and the following disclaimer. | |
42 | * * Redistributions in binary form must reproduce the above copyright | |
43 | * notice, this list of conditions and the following disclaimer in | |
44 | * the documentation and/or other materials provided with the | |
45 | * distribution. | |
46 | * * Neither the name Intel Corporation nor the names of its | |
47 | * contributors may be used to endorse or promote products derived | |
48 | * from this software without specific prior written permission. | |
49 | * | |
50 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
51 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
52 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
53 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
54 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
55 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
56 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
57 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
58 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
59 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
60 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
61 | * | |
62 | *****************************************************************************/ | |
63 | #include "iwl-trans.h" | |
64 | #include "mvm.h" | |
65 | #include "iwl-eeprom-parse.h" | |
66 | #include "iwl-eeprom-read.h" | |
67 | #include "iwl-nvm-parse.h" | |
68 | ||
69 | /* list of NVM sections we are allowed/need to read */ | |
70 | static const int nvm_to_read[] = { | |
71 | NVM_SECTION_TYPE_HW, | |
72 | NVM_SECTION_TYPE_SW, | |
73 | NVM_SECTION_TYPE_CALIBRATION, | |
74 | NVM_SECTION_TYPE_PRODUCTION, | |
75 | }; | |
76 | ||
77 | /* used to simplify the shared operations on NCM_ACCESS_CMD versions */ | |
78 | union iwl_nvm_access_cmd { | |
79 | struct iwl_nvm_access_cmd_ver1 ver1; | |
80 | struct iwl_nvm_access_cmd_ver2 ver2; | |
81 | }; | |
82 | union iwl_nvm_access_resp { | |
83 | struct iwl_nvm_access_resp_ver1 ver1; | |
84 | struct iwl_nvm_access_resp_ver2 ver2; | |
85 | }; | |
86 | ||
87 | static inline void iwl_nvm_fill_read_ver1(struct iwl_nvm_access_cmd_ver1 *cmd, | |
88 | u16 offset, u16 length) | |
89 | { | |
90 | cmd->offset = cpu_to_le16(offset); | |
91 | cmd->length = cpu_to_le16(length); | |
92 | cmd->cache_refresh = 1; | |
93 | } | |
94 | ||
95 | static inline void iwl_nvm_fill_read_ver2(struct iwl_nvm_access_cmd_ver2 *cmd, | |
96 | u16 offset, u16 length, u16 section) | |
97 | { | |
98 | cmd->offset = cpu_to_le16(offset); | |
99 | cmd->length = cpu_to_le16(length); | |
100 | cmd->type = cpu_to_le16(section); | |
101 | } | |
102 | ||
103 | static int iwl_nvm_read_chunk(struct iwl_mvm *mvm, u16 section, | |
104 | u16 offset, u16 length, u8 *data) | |
105 | { | |
106 | union iwl_nvm_access_cmd nvm_access_cmd; | |
107 | union iwl_nvm_access_resp *nvm_resp; | |
108 | struct iwl_rx_packet *pkt; | |
109 | struct iwl_host_cmd cmd = { | |
110 | .id = NVM_ACCESS_CMD, | |
111 | .flags = CMD_SYNC | CMD_WANT_SKB, | |
112 | .data = { &nvm_access_cmd, }, | |
113 | }; | |
114 | int ret, bytes_read, offset_read; | |
115 | u8 *resp_data; | |
116 | ||
117 | memset(&nvm_access_cmd, 0, sizeof(nvm_access_cmd)); | |
118 | ||
119 | /* TODO: not sure family should be the decider, maybe FW version? */ | |
120 | if (mvm->cfg->device_family == IWL_DEVICE_FAMILY_7000) { | |
121 | iwl_nvm_fill_read_ver2(&(nvm_access_cmd.ver2), | |
122 | offset, length, section); | |
123 | cmd.len[0] = sizeof(struct iwl_nvm_access_cmd_ver2); | |
124 | } else { | |
125 | iwl_nvm_fill_read_ver1(&(nvm_access_cmd.ver1), | |
126 | offset, length); | |
127 | cmd.len[0] = sizeof(struct iwl_nvm_access_cmd_ver1); | |
128 | } | |
129 | ||
130 | ret = iwl_mvm_send_cmd(mvm, &cmd); | |
131 | if (ret) | |
132 | return ret; | |
133 | ||
134 | pkt = cmd.resp_pkt; | |
135 | if (pkt->hdr.flags & IWL_CMD_FAILED_MSK) { | |
136 | IWL_ERR(mvm, "Bad return from NVM_ACCES_COMMAND (0x%08X)\n", | |
137 | pkt->hdr.flags); | |
138 | ret = -EIO; | |
139 | goto exit; | |
140 | } | |
141 | ||
142 | /* Extract NVM response */ | |
143 | nvm_resp = (void *)pkt->data; | |
144 | if (mvm->cfg->device_family == IWL_DEVICE_FAMILY_7000) { | |
145 | ret = le16_to_cpu(nvm_resp->ver2.status); | |
146 | bytes_read = le16_to_cpu(nvm_resp->ver2.length); | |
147 | offset_read = le16_to_cpu(nvm_resp->ver2.offset); | |
148 | resp_data = nvm_resp->ver2.data; | |
149 | } else { | |
150 | ret = le16_to_cpu(nvm_resp->ver1.length) <= 0; | |
151 | bytes_read = le16_to_cpu(nvm_resp->ver1.length); | |
152 | offset_read = le16_to_cpu(nvm_resp->ver1.offset); | |
153 | resp_data = nvm_resp->ver1.data; | |
154 | } | |
155 | if (ret) { | |
156 | IWL_ERR(mvm, | |
157 | "NVM access command failed with status %d (device: %s)\n", | |
158 | ret, mvm->cfg->name); | |
159 | ret = -EINVAL; | |
160 | goto exit; | |
161 | } | |
162 | ||
163 | if (offset_read != offset) { | |
164 | IWL_ERR(mvm, "NVM ACCESS response with invalid offset %d\n", | |
165 | offset_read); | |
166 | ret = -EINVAL; | |
167 | goto exit; | |
168 | } | |
169 | ||
170 | /* Write data to NVM */ | |
171 | memcpy(data + offset, resp_data, bytes_read); | |
172 | ret = bytes_read; | |
173 | ||
174 | exit: | |
175 | iwl_free_resp(&cmd); | |
176 | return ret; | |
177 | } | |
178 | ||
179 | /* | |
180 | * Reads an NVM section completely. | |
181 | * NICs prior to 7000 family doesn't have a real NVM, but just read | |
182 | * section 0 which is the EEPROM. Because the EEPROM reading is unlimited | |
183 | * by uCode, we need to manually check in this case that we don't | |
184 | * overflow and try to read more than the EEPROM size. | |
185 | * For 7000 family NICs, we supply the maximal size we can read, and | |
186 | * the uCode fills the response with as much data as we can, | |
187 | * without overflowing, so no check is needed. | |
188 | */ | |
189 | static int iwl_nvm_read_section(struct iwl_mvm *mvm, u16 section, | |
190 | u8 *data) | |
191 | { | |
192 | u16 length, offset = 0; | |
193 | int ret; | |
194 | bool old_eeprom = mvm->cfg->device_family != IWL_DEVICE_FAMILY_7000; | |
195 | ||
196 | length = (iwlwifi_mod_params.amsdu_size_8K ? (8 * 1024) : (4 * 1024)) | |
197 | - sizeof(union iwl_nvm_access_cmd) | |
198 | - sizeof(struct iwl_rx_packet); | |
199 | /* | |
200 | * if length is greater than EEPROM size, truncate it because uCode | |
201 | * doesn't check it by itself, and exit the loop when reached. | |
202 | */ | |
203 | if (old_eeprom && length > mvm->cfg->base_params->eeprom_size) | |
204 | length = mvm->cfg->base_params->eeprom_size; | |
205 | ret = length; | |
206 | ||
207 | /* Read the NVM until exhausted (reading less than requested) */ | |
208 | while (ret == length) { | |
209 | ret = iwl_nvm_read_chunk(mvm, section, offset, length, data); | |
210 | if (ret < 0) { | |
211 | IWL_ERR(mvm, | |
212 | "Cannot read NVM from section %d offset %d, length %d\n", | |
213 | section, offset, length); | |
214 | return ret; | |
215 | } | |
216 | offset += ret; | |
217 | if (old_eeprom && offset == mvm->cfg->base_params->eeprom_size) | |
218 | break; | |
219 | } | |
220 | ||
221 | IWL_INFO(mvm, "NVM section %d read completed\n", section); | |
222 | return offset; | |
223 | } | |
224 | ||
225 | static struct iwl_nvm_data * | |
226 | iwl_parse_nvm_sections(struct iwl_mvm *mvm) | |
227 | { | |
228 | struct iwl_nvm_section *sections = mvm->nvm_sections; | |
229 | const __le16 *hw, *sw, *calib; | |
230 | ||
231 | /* Checking for required sections */ | |
232 | if (!mvm->nvm_sections[NVM_SECTION_TYPE_SW].data || | |
233 | !mvm->nvm_sections[NVM_SECTION_TYPE_HW].data) { | |
234 | IWL_ERR(mvm, "Can't parse empty NVM sections\n"); | |
235 | return NULL; | |
236 | } | |
237 | ||
238 | if (WARN_ON(!mvm->cfg)) | |
239 | return NULL; | |
240 | ||
241 | hw = (const __le16 *)sections[NVM_SECTION_TYPE_HW].data; | |
242 | sw = (const __le16 *)sections[NVM_SECTION_TYPE_SW].data; | |
243 | calib = (const __le16 *)sections[NVM_SECTION_TYPE_CALIBRATION].data; | |
244 | return iwl_parse_nvm_data(mvm->trans->dev, mvm->cfg, hw, sw, calib); | |
245 | } | |
246 | ||
247 | int iwl_nvm_init(struct iwl_mvm *mvm) | |
248 | { | |
249 | int ret, i, section; | |
250 | u8 *nvm_buffer, *temp; | |
251 | ||
252 | if (mvm->cfg->device_family == IWL_DEVICE_FAMILY_7000) { | |
253 | /* TODO: find correct NVM max size for a section */ | |
254 | nvm_buffer = kmalloc(mvm->cfg->base_params->eeprom_size, | |
255 | GFP_KERNEL); | |
256 | if (!nvm_buffer) | |
257 | return -ENOMEM; | |
258 | for (i = 0; i < ARRAY_SIZE(nvm_to_read); i++) { | |
259 | section = nvm_to_read[i]; | |
260 | /* we override the constness for initial read */ | |
261 | ret = iwl_nvm_read_section(mvm, section, nvm_buffer); | |
262 | if (ret < 0) | |
263 | break; | |
264 | temp = kmemdup(nvm_buffer, ret, GFP_KERNEL); | |
265 | if (!temp) { | |
266 | ret = -ENOMEM; | |
267 | break; | |
268 | } | |
269 | mvm->nvm_sections[section].data = temp; | |
270 | mvm->nvm_sections[section].length = ret; | |
271 | } | |
272 | kfree(nvm_buffer); | |
273 | if (ret < 0) | |
274 | return ret; | |
275 | } else { | |
276 | /* allocate eeprom */ | |
277 | mvm->eeprom_blob_size = mvm->cfg->base_params->eeprom_size; | |
278 | IWL_DEBUG_EEPROM(mvm->trans->dev, "NVM size = %zd\n", | |
279 | mvm->eeprom_blob_size); | |
280 | mvm->eeprom_blob = kzalloc(mvm->eeprom_blob_size, GFP_KERNEL); | |
281 | if (!mvm->eeprom_blob) | |
282 | return -ENOMEM; | |
283 | ||
284 | ret = iwl_nvm_read_section(mvm, 0, mvm->eeprom_blob); | |
285 | if (ret != mvm->eeprom_blob_size) { | |
286 | IWL_ERR(mvm, "Read partial NVM %d/%zd\n", | |
287 | ret, mvm->eeprom_blob_size); | |
288 | kfree(mvm->eeprom_blob); | |
289 | mvm->eeprom_blob = NULL; | |
290 | return -EINVAL; | |
291 | } | |
292 | } | |
293 | ||
294 | ret = 0; | |
295 | if (mvm->cfg->device_family == IWL_DEVICE_FAMILY_7000) | |
296 | mvm->nvm_data = iwl_parse_nvm_sections(mvm); | |
297 | else | |
298 | mvm->nvm_data = | |
299 | iwl_parse_eeprom_data(mvm->trans->dev, | |
300 | mvm->cfg, | |
301 | mvm->eeprom_blob, | |
302 | mvm->eeprom_blob_size); | |
303 | ||
304 | if (!mvm->nvm_data) { | |
305 | kfree(mvm->eeprom_blob); | |
306 | mvm->eeprom_blob = NULL; | |
307 | ret = -ENOMEM; | |
308 | } | |
309 | ||
310 | return ret; | |
311 | } |