| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1 | /* | 
 | 2 |  * Intel Wireless WiMAX Connection 2400m | 
 | 3 |  * Firmware uploader | 
 | 4 |  * | 
 | 5 |  * | 
 | 6 |  * Copyright (C) 2007-2008 Intel Corporation. All rights reserved. | 
 | 7 |  * | 
 | 8 |  * Redistribution and use in source and binary forms, with or without | 
 | 9 |  * modification, are permitted provided that the following conditions | 
 | 10 |  * are met: | 
 | 11 |  * | 
 | 12 |  *   * Redistributions of source code must retain the above copyright | 
 | 13 |  *     notice, this list of conditions and the following disclaimer. | 
 | 14 |  *   * Redistributions in binary form must reproduce the above copyright | 
 | 15 |  *     notice, this list of conditions and the following disclaimer in | 
 | 16 |  *     the documentation and/or other materials provided with the | 
 | 17 |  *     distribution. | 
 | 18 |  *   * Neither the name of Intel Corporation nor the names of its | 
 | 19 |  *     contributors may be used to endorse or promote products derived | 
 | 20 |  *     from this software without specific prior written permission. | 
 | 21 |  * | 
 | 22 |  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | 
 | 23 |  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | 
 | 24 |  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 
 | 25 |  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | 
 | 26 |  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 
 | 27 |  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | 
 | 28 |  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 
 | 29 |  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 
 | 30 |  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 
 | 31 |  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 
 | 32 |  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 
 | 33 |  * | 
 | 34 |  * | 
 | 35 |  * Intel Corporation <linux-wimax@intel.com> | 
 | 36 |  * Yanir Lubetkin <yanirx.lubetkin@intel.com> | 
 | 37 |  * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> | 
 | 38 |  *  - Initial implementation | 
 | 39 |  * | 
 | 40 |  * | 
 | 41 |  * THE PROCEDURE | 
 | 42 |  * | 
| Inaky Perez-Gonzalez | aba3792 | 2009-09-03 15:14:29 -0700 | [diff] [blame] | 43 |  * The 2400m and derived devices work in two modes: boot-mode or | 
 | 44 |  * normal mode. In boot mode we can execute only a handful of commands | 
 | 45 |  * targeted at uploading the firmware and launching it. | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 46 |  * | 
 | 47 |  * The 2400m enters boot mode when it is first connected to the | 
 | 48 |  * system, when it crashes and when you ask it to reboot. There are | 
 | 49 |  * two submodes of the boot mode: signed and non-signed. Signed takes | 
 | 50 |  * firmwares signed with a certain private key, non-signed takes any | 
 | 51 |  * firmware. Normal hardware takes only signed firmware. | 
 | 52 |  * | 
| Inaky Perez-Gonzalez | aba3792 | 2009-09-03 15:14:29 -0700 | [diff] [blame] | 53 |  * On boot mode, in USB, we write to the device using the bulk out | 
 | 54 |  * endpoint and read from it in the notification endpoint. In SDIO we | 
 | 55 |  * talk to it via the write address and read from the read address. | 
 | 56 |  * | 
| Lucas De Marchi | 25985ed | 2011-03-30 22:57:33 -0300 | [diff] [blame] | 57 |  * Upon entrance to boot mode, the device sends (preceded with a few | 
| Inaky Perez-Gonzalez | aba3792 | 2009-09-03 15:14:29 -0700 | [diff] [blame] | 58 |  * zero length packets (ZLPs) on the notification endpoint in USB) a | 
 | 59 |  * reboot barker (4 le32 words with the same value). We ack it by | 
 | 60 |  * sending the same barker to the device. The device acks with a | 
 | 61 |  * reboot ack barker (4 le32 words with value I2400M_ACK_BARKER) and | 
 | 62 |  * then is fully booted. At this point we can upload the firmware. | 
 | 63 |  * | 
 | 64 |  * Note that different iterations of the device and EEPROM | 
 | 65 |  * configurations will send different [re]boot barkers; these are | 
 | 66 |  * collected in i2400m_barker_db along with the firmware | 
 | 67 |  * characteristics they require. | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 68 |  * | 
 | 69 |  * This process is accomplished by the i2400m_bootrom_init() | 
 | 70 |  * function. All the device interaction happens through the | 
 | 71 |  * i2400m_bm_cmd() [boot mode command]. Special return values will | 
| Inaky Perez-Gonzalez | aba3792 | 2009-09-03 15:14:29 -0700 | [diff] [blame] | 72 |  * indicate if the device did reset during the process. | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 73 |  * | 
 | 74 |  * After this, we read the MAC address and then (if needed) | 
 | 75 |  * reinitialize the device. We need to read it ahead of time because | 
 | 76 |  * in the future, we might not upload the firmware until userspace | 
 | 77 |  * 'ifconfig up's the device. | 
 | 78 |  * | 
 | 79 |  * We can then upload the firmware file. The file is composed of a BCF | 
 | 80 |  * header (basic data, keys and signatures) and a list of write | 
| Inaky Perez-Gonzalez | bfc4418 | 2009-09-04 17:07:21 -0700 | [diff] [blame] | 81 |  * commands and payloads. Optionally more BCF headers might follow the | 
 | 82 |  * main payload. We first upload the header [i2400m_dnload_init()] and | 
 | 83 |  * then pass the commands and payloads verbatim to the i2400m_bm_cmd() | 
 | 84 |  * function [i2400m_dnload_bcf()]. Then we tell the device to jump to | 
 | 85 |  * the new firmware [i2400m_dnload_finalize()]. | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 86 |  * | 
 | 87 |  * Once firmware is uploaded, we are good to go :) | 
 | 88 |  * | 
 | 89 |  * When we don't know in which mode we are, we first try by sending a | 
 | 90 |  * warm reset request that will take us to boot-mode. If we time out | 
 | 91 |  * waiting for a reboot barker, that means maybe we are already in | 
 | 92 |  * boot mode, so we send a reboot barker. | 
 | 93 |  * | 
 | 94 |  * COMMAND EXECUTION | 
 | 95 |  * | 
 | 96 |  * This code (and process) is single threaded; for executing commands, | 
 | 97 |  * we post a URB to the notification endpoint, post the command, wait | 
 | 98 |  * for data on the notification buffer. We don't need to worry about | 
 | 99 |  * others as we know we are the only ones in there. | 
 | 100 |  * | 
 | 101 |  * BACKEND IMPLEMENTATION | 
 | 102 |  * | 
 | 103 |  * This code is bus-generic; the bus-specific driver provides back end | 
 | 104 |  * implementations to send a boot mode command to the device and to | 
 | 105 |  * read an acknolwedgement from it (or an asynchronous notification) | 
 | 106 |  * from it. | 
 | 107 |  * | 
| Inaky Perez-Gonzalez | 7b43ca7 | 2009-09-14 14:10:16 -0700 | [diff] [blame] | 108 |  * FIRMWARE LOADING | 
 | 109 |  * | 
 | 110 |  * Note that in some cases, we can't just load a firmware file (for | 
 | 111 |  * example, when resuming). For that, we might cache the firmware | 
 | 112 |  * file. Thus, when doing the bootstrap, if there is a cache firmware | 
 | 113 |  * file, it is used; if not, loading from disk is attempted. | 
 | 114 |  * | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 115 |  * ROADMAP | 
 | 116 |  * | 
| Inaky Perez-Gonzalez | aba3792 | 2009-09-03 15:14:29 -0700 | [diff] [blame] | 117 |  * i2400m_barker_db_init              Called by i2400m_driver_init() | 
 | 118 |  *   i2400m_barker_db_add | 
 | 119 |  * | 
 | 120 |  * i2400m_barker_db_exit              Called by i2400m_driver_exit() | 
 | 121 |  * | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 122 |  * i2400m_dev_bootstrap               Called by __i2400m_dev_start() | 
 | 123 |  *   request_firmware | 
| Inaky Perez-Gonzalez | 7b43ca7 | 2009-09-14 14:10:16 -0700 | [diff] [blame] | 124 |  *   i2400m_fw_bootstrap | 
 | 125 |  *     i2400m_fw_check | 
 | 126 |  *       i2400m_fw_hdr_check | 
 | 127 |  *     i2400m_fw_dnload | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 128 |  *   release_firmware | 
 | 129 |  * | 
 | 130 |  * i2400m_fw_dnload | 
 | 131 |  *   i2400m_bootrom_init | 
 | 132 |  *     i2400m_bm_cmd | 
| Inaky Perez-Gonzalez | c931cee | 2009-10-19 16:24:56 +0900 | [diff] [blame] | 133 |  *     i2400m_reset | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 134 |  *   i2400m_dnload_init | 
 | 135 |  *     i2400m_dnload_init_signed | 
 | 136 |  *     i2400m_dnload_init_nonsigned | 
 | 137 |  *       i2400m_download_chunk | 
 | 138 |  *         i2400m_bm_cmd | 
 | 139 |  *   i2400m_dnload_bcf | 
 | 140 |  *     i2400m_bm_cmd | 
 | 141 |  *   i2400m_dnload_finalize | 
 | 142 |  *     i2400m_bm_cmd | 
 | 143 |  * | 
 | 144 |  * i2400m_bm_cmd | 
 | 145 |  *   i2400m->bus_bm_cmd_send() | 
 | 146 |  *   i2400m->bus_bm_wait_for_ack | 
 | 147 |  *   __i2400m_bm_ack_verify | 
| Inaky Perez-Gonzalez | aba3792 | 2009-09-03 15:14:29 -0700 | [diff] [blame] | 148 |  *     i2400m_is_boot_barker | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 149 |  * | 
 | 150 |  * i2400m_bm_cmd_prepare              Used by bus-drivers to prep | 
 | 151 |  *                                    commands before sending | 
| Inaky Perez-Gonzalez | 7b43ca7 | 2009-09-14 14:10:16 -0700 | [diff] [blame] | 152 |  * | 
 | 153 |  * i2400m_pm_notifier                 Called on Power Management events | 
 | 154 |  *   i2400m_fw_cache | 
 | 155 |  *   i2400m_fw_uncache | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 156 |  */ | 
 | 157 | #include <linux/firmware.h> | 
 | 158 | #include <linux/sched.h> | 
| Tejun Heo | 5a0e3ad | 2010-03-24 17:04:11 +0900 | [diff] [blame] | 159 | #include <linux/slab.h> | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 160 | #include <linux/usb.h> | 
 | 161 | #include "i2400m.h" | 
 | 162 |  | 
 | 163 |  | 
 | 164 | #define D_SUBMODULE fw | 
 | 165 | #include "debug-levels.h" | 
 | 166 |  | 
 | 167 |  | 
 | 168 | static const __le32 i2400m_ACK_BARKER[4] = { | 
| Harvey Harrison | ee43777 | 2009-02-01 00:43:54 -0800 | [diff] [blame] | 169 | 	cpu_to_le32(I2400M_ACK_BARKER), | 
 | 170 | 	cpu_to_le32(I2400M_ACK_BARKER), | 
 | 171 | 	cpu_to_le32(I2400M_ACK_BARKER), | 
 | 172 | 	cpu_to_le32(I2400M_ACK_BARKER) | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 173 | }; | 
 | 174 |  | 
 | 175 |  | 
 | 176 | /** | 
 | 177 |  * Prepare a boot-mode command for delivery | 
 | 178 |  * | 
 | 179 |  * @cmd: pointer to bootrom header to prepare | 
 | 180 |  * | 
 | 181 |  * Computes checksum if so needed. After calling this function, DO NOT | 
 | 182 |  * modify the command or header as the checksum won't work anymore. | 
 | 183 |  * | 
 | 184 |  * We do it from here because some times we cannot do it in the | 
 | 185 |  * original context the command was sent (it is a const), so when we | 
 | 186 |  * copy it to our staging buffer, we add the checksum there. | 
 | 187 |  */ | 
 | 188 | void i2400m_bm_cmd_prepare(struct i2400m_bootrom_header *cmd) | 
 | 189 | { | 
 | 190 | 	if (i2400m_brh_get_use_checksum(cmd)) { | 
 | 191 | 		int i; | 
 | 192 | 		u32 checksum = 0; | 
 | 193 | 		const u32 *checksum_ptr = (void *) cmd->payload; | 
 | 194 | 		for (i = 0; i < cmd->data_size / 4; i++) | 
 | 195 | 			checksum += cpu_to_le32(*checksum_ptr++); | 
 | 196 | 		checksum += cmd->command + cmd->target_addr + cmd->data_size; | 
 | 197 | 		cmd->block_checksum = cpu_to_le32(checksum); | 
 | 198 | 	} | 
 | 199 | } | 
 | 200 | EXPORT_SYMBOL_GPL(i2400m_bm_cmd_prepare); | 
 | 201 |  | 
 | 202 |  | 
 | 203 | /* | 
| Inaky Perez-Gonzalez | aba3792 | 2009-09-03 15:14:29 -0700 | [diff] [blame] | 204 |  * Database of known barkers. | 
 | 205 |  * | 
 | 206 |  * A barker is what the device sends indicating he is ready to be | 
 | 207 |  * bootloaded. Different versions of the device will send different | 
 | 208 |  * barkers. Depending on the barker, it might mean the device wants | 
 | 209 |  * some kind of firmware or the other. | 
 | 210 |  */ | 
 | 211 | static struct i2400m_barker_db { | 
 | 212 | 	__le32 data[4]; | 
 | 213 | } *i2400m_barker_db; | 
 | 214 | static size_t i2400m_barker_db_used, i2400m_barker_db_size; | 
 | 215 |  | 
 | 216 |  | 
 | 217 | static | 
 | 218 | int i2400m_zrealloc_2x(void **ptr, size_t *_count, size_t el_size, | 
 | 219 | 		       gfp_t gfp_flags) | 
 | 220 | { | 
 | 221 | 	size_t old_count = *_count, | 
 | 222 | 		new_count = old_count ? 2 * old_count : 2, | 
 | 223 | 		old_size = el_size * old_count, | 
 | 224 | 		new_size = el_size * new_count; | 
 | 225 | 	void *nptr = krealloc(*ptr, new_size, gfp_flags); | 
 | 226 | 	if (nptr) { | 
 | 227 | 		/* zero the other half or the whole thing if old_count | 
 | 228 | 		 * was zero */ | 
 | 229 | 		if (old_size == 0) | 
 | 230 | 			memset(nptr, 0, new_size); | 
 | 231 | 		else | 
 | 232 | 			memset(nptr + old_size, 0, old_size); | 
 | 233 | 		*_count = new_count; | 
 | 234 | 		*ptr = nptr; | 
 | 235 | 		return 0; | 
 | 236 | 	} else | 
 | 237 | 		return -ENOMEM; | 
 | 238 | } | 
 | 239 |  | 
 | 240 |  | 
 | 241 | /* | 
 | 242 |  * Add a barker to the database | 
 | 243 |  * | 
 | 244 |  * This cannot used outside of this module and only at at module_init | 
 | 245 |  * time. This is to avoid the need to do locking. | 
 | 246 |  */ | 
 | 247 | static | 
 | 248 | int i2400m_barker_db_add(u32 barker_id) | 
 | 249 | { | 
 | 250 | 	int result; | 
 | 251 |  | 
 | 252 | 	struct i2400m_barker_db *barker; | 
 | 253 | 	if (i2400m_barker_db_used >= i2400m_barker_db_size) { | 
 | 254 | 		result = i2400m_zrealloc_2x( | 
 | 255 | 			(void **) &i2400m_barker_db, &i2400m_barker_db_size, | 
 | 256 | 			sizeof(i2400m_barker_db[0]), GFP_KERNEL); | 
 | 257 | 		if (result < 0) | 
 | 258 | 			return result; | 
 | 259 | 	} | 
 | 260 | 	barker = i2400m_barker_db + i2400m_barker_db_used++; | 
 | 261 | 	barker->data[0] = le32_to_cpu(barker_id); | 
 | 262 | 	barker->data[1] = le32_to_cpu(barker_id); | 
 | 263 | 	barker->data[2] = le32_to_cpu(barker_id); | 
 | 264 | 	barker->data[3] = le32_to_cpu(barker_id); | 
 | 265 | 	return 0; | 
 | 266 | } | 
 | 267 |  | 
 | 268 |  | 
 | 269 | void i2400m_barker_db_exit(void) | 
 | 270 | { | 
 | 271 | 	kfree(i2400m_barker_db); | 
 | 272 | 	i2400m_barker_db = NULL; | 
 | 273 | 	i2400m_barker_db_size = 0; | 
 | 274 | 	i2400m_barker_db_used = 0; | 
 | 275 | } | 
 | 276 |  | 
 | 277 |  | 
 | 278 | /* | 
 | 279 |  * Helper function to add all the known stable barkers to the barker | 
 | 280 |  * database. | 
 | 281 |  */ | 
 | 282 | static | 
 | 283 | int i2400m_barker_db_known_barkers(void) | 
 | 284 | { | 
 | 285 | 	int result; | 
 | 286 |  | 
 | 287 | 	result = i2400m_barker_db_add(I2400M_NBOOT_BARKER); | 
 | 288 | 	if (result < 0) | 
 | 289 | 		goto error_add; | 
 | 290 | 	result = i2400m_barker_db_add(I2400M_SBOOT_BARKER); | 
 | 291 | 	if (result < 0) | 
 | 292 | 		goto error_add; | 
| Dirk Brandewie | 7329012 | 2009-08-12 11:29:46 -0700 | [diff] [blame] | 293 | 	result = i2400m_barker_db_add(I2400M_SBOOT_BARKER_6050); | 
 | 294 | 	if (result < 0) | 
 | 295 | 		goto error_add; | 
| Inaky Perez-Gonzalez | aba3792 | 2009-09-03 15:14:29 -0700 | [diff] [blame] | 296 | error_add: | 
 | 297 |        return result; | 
 | 298 | } | 
 | 299 |  | 
 | 300 |  | 
 | 301 | /* | 
 | 302 |  * Initialize the barker database | 
 | 303 |  * | 
 | 304 |  * This can only be used from the module_init function for this | 
 | 305 |  * module; this is to avoid the need to do locking. | 
 | 306 |  * | 
 | 307 |  * @options: command line argument with extra barkers to | 
 | 308 |  *     recognize. This is a comma-separated list of 32-bit hex | 
 | 309 |  *     numbers. They are appended to the existing list. Setting 0 | 
 | 310 |  *     cleans the existing list and starts a new one. | 
 | 311 |  */ | 
 | 312 | int i2400m_barker_db_init(const char *_options) | 
 | 313 | { | 
 | 314 | 	int result; | 
 | 315 | 	char *options = NULL, *options_orig, *token; | 
 | 316 |  | 
 | 317 | 	i2400m_barker_db = NULL; | 
 | 318 | 	i2400m_barker_db_size = 0; | 
 | 319 | 	i2400m_barker_db_used = 0; | 
 | 320 |  | 
 | 321 | 	result = i2400m_barker_db_known_barkers(); | 
 | 322 | 	if (result < 0) | 
 | 323 | 		goto error_add; | 
 | 324 | 	/* parse command line options from i2400m.barkers */ | 
 | 325 | 	if (_options != NULL) { | 
 | 326 | 		unsigned barker; | 
 | 327 |  | 
 | 328 | 		options_orig = kstrdup(_options, GFP_KERNEL); | 
 | 329 | 		if (options_orig == NULL) | 
 | 330 | 			goto error_parse; | 
 | 331 | 		options = options_orig; | 
 | 332 |  | 
 | 333 | 		while ((token = strsep(&options, ",")) != NULL) { | 
 | 334 | 			if (*token == '\0')	/* eat joint commas */ | 
 | 335 | 				continue; | 
 | 336 | 			if (sscanf(token, "%x", &barker) != 1 | 
 | 337 | 			    || barker > 0xffffffff) { | 
 | 338 | 				printk(KERN_ERR "%s: can't recognize " | 
 | 339 | 				       "i2400m.barkers value '%s' as " | 
 | 340 | 				       "a 32-bit number\n", | 
 | 341 | 				       __func__, token); | 
 | 342 | 				result = -EINVAL; | 
 | 343 | 				goto error_parse; | 
 | 344 | 			} | 
 | 345 | 			if (barker == 0) { | 
 | 346 | 				/* clean list and start new */ | 
 | 347 | 				i2400m_barker_db_exit(); | 
 | 348 | 				continue; | 
 | 349 | 			} | 
 | 350 | 			result = i2400m_barker_db_add(barker); | 
 | 351 | 			if (result < 0) | 
 | 352 | 				goto error_add; | 
 | 353 | 		} | 
 | 354 | 		kfree(options_orig); | 
 | 355 | 	} | 
 | 356 | 	return 0; | 
 | 357 |  | 
 | 358 | error_parse: | 
 | 359 | error_add: | 
 | 360 | 	kfree(i2400m_barker_db); | 
 | 361 | 	return result; | 
 | 362 | } | 
 | 363 |  | 
 | 364 |  | 
 | 365 | /* | 
 | 366 |  * Recognize a boot barker | 
 | 367 |  * | 
 | 368 |  * @buf: buffer where the boot barker. | 
 | 369 |  * @buf_size: size of the buffer (has to be 16 bytes). It is passed | 
 | 370 |  *     here so the function can check it for the caller. | 
 | 371 |  * | 
 | 372 |  * Note that as a side effect, upon identifying the obtained boot | 
 | 373 |  * barker, this function will set i2400m->barker to point to the right | 
 | 374 |  * barker database entry. Subsequent calls to the function will result | 
 | 375 |  * in verifying that the same type of boot barker is returned when the | 
 | 376 |  * device [re]boots (as long as the same device instance is used). | 
 | 377 |  * | 
 | 378 |  * Return: 0 if @buf matches a known boot barker. -ENOENT if the | 
 | 379 |  *     buffer in @buf doesn't match any boot barker in the database or | 
 | 380 |  *     -EILSEQ if the buffer doesn't have the right size. | 
 | 381 |  */ | 
 | 382 | int i2400m_is_boot_barker(struct i2400m *i2400m, | 
 | 383 | 			  const void *buf, size_t buf_size) | 
 | 384 | { | 
 | 385 | 	int result; | 
 | 386 | 	struct device *dev = i2400m_dev(i2400m); | 
 | 387 | 	struct i2400m_barker_db *barker; | 
 | 388 | 	int i; | 
 | 389 |  | 
 | 390 | 	result = -ENOENT; | 
 | 391 | 	if (buf_size != sizeof(i2400m_barker_db[i].data)) | 
 | 392 | 		return result; | 
 | 393 |  | 
 | 394 | 	/* Short circuit if we have already discovered the barker | 
 | 395 | 	 * associated with the device. */ | 
 | 396 | 	if (i2400m->barker | 
 | 397 | 	    && !memcmp(buf, i2400m->barker, sizeof(i2400m->barker->data))) { | 
 | 398 | 		unsigned index = (i2400m->barker - i2400m_barker_db) | 
 | 399 | 			/ sizeof(*i2400m->barker); | 
 | 400 | 		d_printf(2, dev, "boot barker cache-confirmed #%u/%08x\n", | 
 | 401 | 			 index, le32_to_cpu(i2400m->barker->data[0])); | 
 | 402 | 		return 0; | 
 | 403 | 	} | 
 | 404 |  | 
 | 405 | 	for (i = 0; i < i2400m_barker_db_used; i++) { | 
 | 406 | 		barker = &i2400m_barker_db[i]; | 
 | 407 | 		BUILD_BUG_ON(sizeof(barker->data) != 16); | 
 | 408 | 		if (memcmp(buf, barker->data, sizeof(barker->data))) | 
 | 409 | 			continue; | 
 | 410 |  | 
 | 411 | 		if (i2400m->barker == NULL) { | 
 | 412 | 			i2400m->barker = barker; | 
 | 413 | 			d_printf(1, dev, "boot barker set to #%u/%08x\n", | 
 | 414 | 				 i, le32_to_cpu(barker->data[0])); | 
 | 415 | 			if (barker->data[0] == le32_to_cpu(I2400M_NBOOT_BARKER)) | 
 | 416 | 				i2400m->sboot = 0; | 
 | 417 | 			else | 
 | 418 | 				i2400m->sboot = 1; | 
 | 419 | 		} else if (i2400m->barker != barker) { | 
 | 420 | 			dev_err(dev, "HW inconsistency: device " | 
 | 421 | 				"reports a different boot barker " | 
 | 422 | 				"than set (from %08x to %08x)\n", | 
 | 423 | 				le32_to_cpu(i2400m->barker->data[0]), | 
 | 424 | 				le32_to_cpu(barker->data[0])); | 
 | 425 | 			result = -EIO; | 
 | 426 | 		} else | 
 | 427 | 			d_printf(2, dev, "boot barker confirmed #%u/%08x\n", | 
 | 428 | 				 i, le32_to_cpu(barker->data[0])); | 
 | 429 | 		result = 0; | 
 | 430 | 		break; | 
 | 431 | 	} | 
 | 432 | 	return result; | 
 | 433 | } | 
 | 434 | EXPORT_SYMBOL_GPL(i2400m_is_boot_barker); | 
 | 435 |  | 
 | 436 |  | 
 | 437 | /* | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 438 |  * Verify the ack data received | 
 | 439 |  * | 
 | 440 |  * Given a reply to a boot mode command, chew it and verify everything | 
 | 441 |  * is ok. | 
 | 442 |  * | 
 | 443 |  * @opcode: opcode which generated this ack. For error messages. | 
 | 444 |  * @ack: pointer to ack data we received | 
 | 445 |  * @ack_size: size of that data buffer | 
 | 446 |  * @flags: I2400M_BM_CMD_* flags we called the command with. | 
 | 447 |  * | 
 | 448 |  * Way too long function -- maybe it should be further split | 
 | 449 |  */ | 
 | 450 | static | 
 | 451 | ssize_t __i2400m_bm_ack_verify(struct i2400m *i2400m, int opcode, | 
 | 452 | 			       struct i2400m_bootrom_header *ack, | 
 | 453 | 			       size_t ack_size, int flags) | 
 | 454 | { | 
 | 455 | 	ssize_t result = -ENOMEM; | 
 | 456 | 	struct device *dev = i2400m_dev(i2400m); | 
 | 457 |  | 
 | 458 | 	d_fnstart(8, dev, "(i2400m %p opcode %d ack %p size %zu)\n", | 
 | 459 | 		  i2400m, opcode, ack, ack_size); | 
 | 460 | 	if (ack_size < sizeof(*ack)) { | 
 | 461 | 		result = -EIO; | 
 | 462 | 		dev_err(dev, "boot-mode cmd %d: HW BUG? notification didn't " | 
 | 463 | 			"return enough data (%zu bytes vs %zu expected)\n", | 
 | 464 | 			opcode, ack_size, sizeof(*ack)); | 
 | 465 | 		goto error_ack_short; | 
 | 466 | 	} | 
| Inaky Perez-Gonzalez | aba3792 | 2009-09-03 15:14:29 -0700 | [diff] [blame] | 467 | 	result = i2400m_is_boot_barker(i2400m, ack, ack_size); | 
 | 468 | 	if (result >= 0) { | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 469 | 		result = -ERESTARTSYS; | 
| Inaky Perez-Gonzalez | aba3792 | 2009-09-03 15:14:29 -0700 | [diff] [blame] | 470 | 		d_printf(6, dev, "boot-mode cmd %d: HW boot barker\n", opcode); | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 471 | 		goto error_reboot; | 
 | 472 | 	} | 
 | 473 | 	if (ack_size == sizeof(i2400m_ACK_BARKER) | 
 | 474 | 		 && memcmp(ack, i2400m_ACK_BARKER, sizeof(*ack)) == 0) { | 
 | 475 | 		result = -EISCONN; | 
 | 476 | 		d_printf(3, dev, "boot-mode cmd %d: HW reboot ack barker\n", | 
 | 477 | 			 opcode); | 
 | 478 | 		goto error_reboot_ack; | 
 | 479 | 	} | 
 | 480 | 	result = 0; | 
 | 481 | 	if (flags & I2400M_BM_CMD_RAW) | 
 | 482 | 		goto out_raw; | 
 | 483 | 	ack->data_size = le32_to_cpu(ack->data_size); | 
 | 484 | 	ack->target_addr = le32_to_cpu(ack->target_addr); | 
 | 485 | 	ack->block_checksum = le32_to_cpu(ack->block_checksum); | 
 | 486 | 	d_printf(5, dev, "boot-mode cmd %d: notification for opcode %u " | 
 | 487 | 		 "response %u csum %u rr %u da %u\n", | 
 | 488 | 		 opcode, i2400m_brh_get_opcode(ack), | 
 | 489 | 		 i2400m_brh_get_response(ack), | 
 | 490 | 		 i2400m_brh_get_use_checksum(ack), | 
 | 491 | 		 i2400m_brh_get_response_required(ack), | 
 | 492 | 		 i2400m_brh_get_direct_access(ack)); | 
 | 493 | 	result = -EIO; | 
 | 494 | 	if (i2400m_brh_get_signature(ack) != 0xcbbc) { | 
 | 495 | 		dev_err(dev, "boot-mode cmd %d: HW BUG? wrong signature " | 
 | 496 | 			"0x%04x\n", opcode, i2400m_brh_get_signature(ack)); | 
 | 497 | 		goto error_ack_signature; | 
 | 498 | 	} | 
 | 499 | 	if (opcode != -1 && opcode != i2400m_brh_get_opcode(ack)) { | 
 | 500 | 		dev_err(dev, "boot-mode cmd %d: HW BUG? " | 
 | 501 | 			"received response for opcode %u, expected %u\n", | 
 | 502 | 			opcode, i2400m_brh_get_opcode(ack), opcode); | 
 | 503 | 		goto error_ack_opcode; | 
 | 504 | 	} | 
 | 505 | 	if (i2400m_brh_get_response(ack) != 0) {	/* failed? */ | 
 | 506 | 		dev_err(dev, "boot-mode cmd %d: error; hw response %u\n", | 
 | 507 | 			opcode, i2400m_brh_get_response(ack)); | 
 | 508 | 		goto error_ack_failed; | 
 | 509 | 	} | 
 | 510 | 	if (ack_size < ack->data_size + sizeof(*ack)) { | 
 | 511 | 		dev_err(dev, "boot-mode cmd %d: SW BUG " | 
 | 512 | 			"driver provided only %zu bytes for %zu bytes " | 
 | 513 | 			"of data\n", opcode, ack_size, | 
 | 514 | 			(size_t) le32_to_cpu(ack->data_size) + sizeof(*ack)); | 
 | 515 | 		goto error_ack_short_buffer; | 
 | 516 | 	} | 
 | 517 | 	result = ack_size; | 
 | 518 | 	/* Don't you love this stack of empty targets? Well, I don't | 
 | 519 | 	 * either, but it helps track exactly who comes in here and | 
 | 520 | 	 * why :) */ | 
 | 521 | error_ack_short_buffer: | 
 | 522 | error_ack_failed: | 
 | 523 | error_ack_opcode: | 
 | 524 | error_ack_signature: | 
 | 525 | out_raw: | 
 | 526 | error_reboot_ack: | 
 | 527 | error_reboot: | 
 | 528 | error_ack_short: | 
 | 529 | 	d_fnend(8, dev, "(i2400m %p opcode %d ack %p size %zu) = %d\n", | 
 | 530 | 		i2400m, opcode, ack, ack_size, (int) result); | 
 | 531 | 	return result; | 
 | 532 | } | 
 | 533 |  | 
 | 534 |  | 
 | 535 | /** | 
 | 536 |  * i2400m_bm_cmd - Execute a boot mode command | 
 | 537 |  * | 
 | 538 |  * @cmd: buffer containing the command data (pointing at the header). | 
 | 539 |  *     This data can be ANYWHERE (for USB, we will copy it to an | 
 | 540 |  *     specific buffer). Make sure everything is in proper little | 
 | 541 |  *     endian. | 
 | 542 |  * | 
 | 543 |  *     A raw buffer can be also sent, just cast it and set flags to | 
 | 544 |  *     I2400M_BM_CMD_RAW. | 
 | 545 |  * | 
 | 546 |  *     This function will generate a checksum for you if the | 
 | 547 |  *     checksum bit in the command is set (unless I2400M_BM_CMD_RAW | 
 | 548 |  *     is set). | 
 | 549 |  * | 
 | 550 |  *     You can use the i2400m->bm_cmd_buf to stage your commands and | 
 | 551 |  *     send them. | 
 | 552 |  * | 
 | 553 |  *     If NULL, no command is sent (we just wait for an ack). | 
 | 554 |  * | 
 | 555 |  * @cmd_size: size of the command. Will be auto padded to the | 
 | 556 |  *     bus-specific drivers padding requirements. | 
 | 557 |  * | 
 | 558 |  * @ack: buffer where to place the acknowledgement. If it is a regular | 
 | 559 |  *     command response, all fields will be returned with the right, | 
 | 560 |  *     native endianess. | 
 | 561 |  * | 
 | 562 |  *     You *cannot* use i2400m->bm_ack_buf for this buffer. | 
 | 563 |  * | 
 | 564 |  * @ack_size: size of @ack, 16 aligned; you need to provide at least | 
 | 565 |  *     sizeof(*ack) bytes and then enough to contain the return data | 
 | 566 |  *     from the command | 
 | 567 |  * | 
 | 568 |  * @flags: see I2400M_BM_CMD_* above. | 
 | 569 |  * | 
 | 570 |  * @returns: bytes received by the notification; if < 0, an errno code | 
 | 571 |  *     denoting an error or: | 
 | 572 |  * | 
 | 573 |  *     -ERESTARTSYS  The device has rebooted | 
 | 574 |  * | 
 | 575 |  * Executes a boot-mode command and waits for a response, doing basic | 
 | 576 |  * validation on it; if a zero length response is received, it retries | 
 | 577 |  * waiting for a response until a non-zero one is received (timing out | 
 | 578 |  * after %I2400M_BOOT_RETRIES retries). | 
 | 579 |  */ | 
 | 580 | static | 
 | 581 | ssize_t i2400m_bm_cmd(struct i2400m *i2400m, | 
 | 582 | 		      const struct i2400m_bootrom_header *cmd, size_t cmd_size, | 
 | 583 | 		      struct i2400m_bootrom_header *ack, size_t ack_size, | 
 | 584 | 		      int flags) | 
 | 585 | { | 
 | 586 | 	ssize_t result = -ENOMEM, rx_bytes; | 
 | 587 | 	struct device *dev = i2400m_dev(i2400m); | 
 | 588 | 	int opcode = cmd == NULL ? -1 : i2400m_brh_get_opcode(cmd); | 
 | 589 |  | 
 | 590 | 	d_fnstart(6, dev, "(i2400m %p cmd %p size %zu ack %p size %zu)\n", | 
 | 591 | 		  i2400m, cmd, cmd_size, ack, ack_size); | 
 | 592 | 	BUG_ON(ack_size < sizeof(*ack)); | 
 | 593 | 	BUG_ON(i2400m->boot_mode == 0); | 
 | 594 |  | 
 | 595 | 	if (cmd != NULL) {		/* send the command */ | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 596 | 		result = i2400m->bus_bm_cmd_send(i2400m, cmd, cmd_size, flags); | 
 | 597 | 		if (result < 0) | 
 | 598 | 			goto error_cmd_send; | 
 | 599 | 		if ((flags & I2400M_BM_CMD_RAW) == 0) | 
 | 600 | 			d_printf(5, dev, | 
 | 601 | 				 "boot-mode cmd %d csum %u rr %u da %u: " | 
 | 602 | 				 "addr 0x%04x size %u block csum 0x%04x\n", | 
 | 603 | 				 opcode, i2400m_brh_get_use_checksum(cmd), | 
 | 604 | 				 i2400m_brh_get_response_required(cmd), | 
 | 605 | 				 i2400m_brh_get_direct_access(cmd), | 
 | 606 | 				 cmd->target_addr, cmd->data_size, | 
 | 607 | 				 cmd->block_checksum); | 
 | 608 | 	} | 
 | 609 | 	result = i2400m->bus_bm_wait_for_ack(i2400m, ack, ack_size); | 
 | 610 | 	if (result < 0) { | 
 | 611 | 		dev_err(dev, "boot-mode cmd %d: error waiting for an ack: %d\n", | 
 | 612 | 			opcode, (int) result);	/* bah, %zd doesn't work */ | 
 | 613 | 		goto error_wait_for_ack; | 
 | 614 | 	} | 
 | 615 | 	rx_bytes = result; | 
| Daniel Mack | 3ad2f3f | 2010-02-03 08:01:28 +0800 | [diff] [blame] | 616 | 	/* verify the ack and read more if necessary [result is the | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 617 | 	 * final amount of bytes we get in the ack]  */ | 
 | 618 | 	result = __i2400m_bm_ack_verify(i2400m, opcode, ack, ack_size, flags); | 
 | 619 | 	if (result < 0) | 
 | 620 | 		goto error_bad_ack; | 
 | 621 | 	/* Don't you love this stack of empty targets? Well, I don't | 
 | 622 | 	 * either, but it helps track exactly who comes in here and | 
 | 623 | 	 * why :) */ | 
 | 624 | 	result = rx_bytes; | 
 | 625 | error_bad_ack: | 
 | 626 | error_wait_for_ack: | 
 | 627 | error_cmd_send: | 
 | 628 | 	d_fnend(6, dev, "(i2400m %p cmd %p size %zu ack %p size %zu) = %d\n", | 
 | 629 | 		i2400m, cmd, cmd_size, ack, ack_size, (int) result); | 
 | 630 | 	return result; | 
 | 631 | } | 
 | 632 |  | 
 | 633 |  | 
 | 634 | /** | 
 | 635 |  * i2400m_download_chunk - write a single chunk of data to the device's memory | 
 | 636 |  * | 
 | 637 |  * @i2400m: device descriptor | 
 | 638 |  * @buf: the buffer to write | 
 | 639 |  * @buf_len: length of the buffer to write | 
 | 640 |  * @addr: address in the device memory space | 
 | 641 |  * @direct: bootrom write mode | 
 | 642 |  * @do_csum: should a checksum validation be performed | 
 | 643 |  */ | 
 | 644 | static int i2400m_download_chunk(struct i2400m *i2400m, const void *chunk, | 
 | 645 | 				 size_t __chunk_len, unsigned long addr, | 
 | 646 | 				 unsigned int direct, unsigned int do_csum) | 
 | 647 | { | 
 | 648 | 	int ret; | 
| Inaky Perez-Gonzalez | 8593a19 | 2009-05-20 16:53:30 -0700 | [diff] [blame] | 649 | 	size_t chunk_len = ALIGN(__chunk_len, I2400M_PL_ALIGN); | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 650 | 	struct device *dev = i2400m_dev(i2400m); | 
 | 651 | 	struct { | 
 | 652 | 		struct i2400m_bootrom_header cmd; | 
 | 653 | 		u8 cmd_payload[chunk_len]; | 
| Eric Dumazet | ba2d358 | 2010-06-02 18:10:09 +0000 | [diff] [blame] | 654 | 	} __packed *buf; | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 655 | 	struct i2400m_bootrom_header ack; | 
 | 656 |  | 
 | 657 | 	d_fnstart(5, dev, "(i2400m %p chunk %p __chunk_len %zu addr 0x%08lx " | 
 | 658 | 		  "direct %u do_csum %u)\n", i2400m, chunk, __chunk_len, | 
 | 659 | 		  addr, direct, do_csum); | 
 | 660 | 	buf = i2400m->bm_cmd_buf; | 
 | 661 | 	memcpy(buf->cmd_payload, chunk, __chunk_len); | 
 | 662 | 	memset(buf->cmd_payload + __chunk_len, 0xad, chunk_len - __chunk_len); | 
 | 663 |  | 
 | 664 | 	buf->cmd.command = i2400m_brh_command(I2400M_BRH_WRITE, | 
 | 665 | 					      __chunk_len & 0x3 ? 0 : do_csum, | 
 | 666 | 					      __chunk_len & 0xf ? 0 : direct); | 
 | 667 | 	buf->cmd.target_addr = cpu_to_le32(addr); | 
 | 668 | 	buf->cmd.data_size = cpu_to_le32(__chunk_len); | 
 | 669 | 	ret = i2400m_bm_cmd(i2400m, &buf->cmd, sizeof(buf->cmd) + chunk_len, | 
 | 670 | 			    &ack, sizeof(ack), 0); | 
 | 671 | 	if (ret >= 0) | 
 | 672 | 		ret = 0; | 
 | 673 | 	d_fnend(5, dev, "(i2400m %p chunk %p __chunk_len %zu addr 0x%08lx " | 
 | 674 | 		"direct %u do_csum %u) = %d\n", i2400m, chunk, __chunk_len, | 
 | 675 | 		addr, direct, do_csum, ret); | 
 | 676 | 	return ret; | 
 | 677 | } | 
 | 678 |  | 
 | 679 |  | 
 | 680 | /* | 
 | 681 |  * Download a BCF file's sections to the device | 
 | 682 |  * | 
 | 683 |  * @i2400m: device descriptor | 
| Inaky Perez-Gonzalez | 10607c8 | 2009-09-09 17:11:57 -0700 | [diff] [blame] | 684 |  * @bcf: pointer to firmware data (first header followed by the | 
 | 685 |  *     payloads). Assumed verified and consistent. | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 686 |  * @bcf_len: length (in bytes) of the @bcf buffer. | 
 | 687 |  * | 
 | 688 |  * Returns: < 0 errno code on error or the offset to the jump instruction. | 
 | 689 |  * | 
 | 690 |  * Given a BCF file, downloads each section (a command and a payload) | 
 | 691 |  * to the device's address space. Actually, it just executes each | 
 | 692 |  * command i the BCF file. | 
 | 693 |  * | 
 | 694 |  * The section size has to be aligned to 4 bytes AND the padding has | 
 | 695 |  * to be taken from the firmware file, as the signature takes it into | 
 | 696 |  * account. | 
 | 697 |  */ | 
 | 698 | static | 
 | 699 | ssize_t i2400m_dnload_bcf(struct i2400m *i2400m, | 
 | 700 | 			  const struct i2400m_bcf_hdr *bcf, size_t bcf_len) | 
 | 701 | { | 
 | 702 | 	ssize_t ret; | 
 | 703 | 	struct device *dev = i2400m_dev(i2400m); | 
 | 704 | 	size_t offset,		/* iterator offset */ | 
 | 705 | 		data_size,	/* Size of the data payload */ | 
 | 706 | 		section_size,	/* Size of the whole section (cmd + payload) */ | 
 | 707 | 		section = 1; | 
 | 708 | 	const struct i2400m_bootrom_header *bh; | 
 | 709 | 	struct i2400m_bootrom_header ack; | 
 | 710 |  | 
 | 711 | 	d_fnstart(3, dev, "(i2400m %p bcf %p bcf_len %zu)\n", | 
 | 712 | 		  i2400m, bcf, bcf_len); | 
 | 713 | 	/* Iterate over the command blocks in the BCF file that start | 
 | 714 | 	 * after the header */ | 
 | 715 | 	offset = le32_to_cpu(bcf->header_len) * sizeof(u32); | 
 | 716 | 	while (1) {	/* start sending the file */ | 
 | 717 | 		bh = (void *) bcf + offset; | 
 | 718 | 		data_size = le32_to_cpu(bh->data_size); | 
 | 719 | 		section_size = ALIGN(sizeof(*bh) + data_size, 4); | 
 | 720 | 		d_printf(7, dev, | 
 | 721 | 			 "downloading section #%zu (@%zu %zu B) to 0x%08x\n", | 
 | 722 | 			 section, offset, sizeof(*bh) + data_size, | 
 | 723 | 			 le32_to_cpu(bh->target_addr)); | 
| Cindy H Kao | 6f4fc90 | 2009-10-02 19:18:43 -0700 | [diff] [blame] | 724 | 		/* | 
 | 725 | 		 * We look for JUMP cmd from the bootmode header, | 
 | 726 | 		 * either I2400M_BRH_SIGNED_JUMP for secure boot | 
 | 727 | 		 * or I2400M_BRH_JUMP for unsecure boot, the last chunk | 
 | 728 | 		 * should be the bootmode header with JUMP cmd. | 
 | 729 | 		 */ | 
 | 730 | 		if (i2400m_brh_get_opcode(bh) == I2400M_BRH_SIGNED_JUMP || | 
 | 731 | 			i2400m_brh_get_opcode(bh) == I2400M_BRH_JUMP) { | 
 | 732 | 			d_printf(5, dev,  "jump found @%zu\n", offset); | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 733 | 			break; | 
 | 734 | 		} | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 735 | 		if (offset + section_size > bcf_len) { | 
 | 736 | 			dev_err(dev, "fw %s: bad section #%zu, " | 
 | 737 | 				"end (@%zu) beyond EOF (@%zu)\n", | 
| Inaky Perez-Gonzalez | 1039abb | 2009-02-28 23:42:47 +0000 | [diff] [blame] | 738 | 				i2400m->fw_name, section, | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 739 | 				offset + section_size,  bcf_len); | 
 | 740 | 			ret = -EINVAL; | 
 | 741 | 			goto error_section_beyond_eof; | 
 | 742 | 		} | 
 | 743 | 		__i2400m_msleep(20); | 
 | 744 | 		ret = i2400m_bm_cmd(i2400m, bh, section_size, | 
 | 745 | 				    &ack, sizeof(ack), I2400M_BM_CMD_RAW); | 
 | 746 | 		if (ret < 0) { | 
 | 747 | 			dev_err(dev, "fw %s: section #%zu (@%zu %zu B) " | 
| Inaky Perez-Gonzalez | 1039abb | 2009-02-28 23:42:47 +0000 | [diff] [blame] | 748 | 				"failed %d\n", i2400m->fw_name, section, | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 749 | 				offset, sizeof(*bh) + data_size, (int) ret); | 
 | 750 | 			goto error_send; | 
 | 751 | 		} | 
 | 752 | 		offset += section_size; | 
 | 753 | 		section++; | 
 | 754 | 	} | 
 | 755 | 	ret = offset; | 
 | 756 | error_section_beyond_eof: | 
 | 757 | error_send: | 
 | 758 | 	d_fnend(3, dev, "(i2400m %p bcf %p bcf_len %zu) = %d\n", | 
 | 759 | 		i2400m, bcf, bcf_len, (int) ret); | 
 | 760 | 	return ret; | 
 | 761 | } | 
 | 762 |  | 
 | 763 |  | 
 | 764 | /* | 
| Inaky Perez-Gonzalez | 32742e6 | 2009-09-03 15:56:40 -0700 | [diff] [blame] | 765 |  * Indicate if the device emitted a reboot barker that indicates | 
 | 766 |  * "signed boot" | 
 | 767 |  */ | 
 | 768 | static | 
 | 769 | unsigned i2400m_boot_is_signed(struct i2400m *i2400m) | 
 | 770 | { | 
 | 771 | 	return likely(i2400m->sboot); | 
 | 772 | } | 
 | 773 |  | 
 | 774 |  | 
 | 775 | /* | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 776 |  * Do the final steps of uploading firmware | 
 | 777 |  * | 
| Inaky Perez-Gonzalez | 10607c8 | 2009-09-09 17:11:57 -0700 | [diff] [blame] | 778 |  * @bcf_hdr: BCF header we are actually using | 
 | 779 |  * @bcf: pointer to the firmware image (which matches the first header | 
 | 780 |  *     that is followed by the actual payloads). | 
 | 781 |  * @offset: [byte] offset into @bcf for the command we need to send. | 
 | 782 |  * | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 783 |  * Depending on the boot mode (signed vs non-signed), different | 
 | 784 |  * actions need to be taken. | 
 | 785 |  */ | 
 | 786 | static | 
 | 787 | int i2400m_dnload_finalize(struct i2400m *i2400m, | 
| Inaky Perez-Gonzalez | 10607c8 | 2009-09-09 17:11:57 -0700 | [diff] [blame] | 788 | 			   const struct i2400m_bcf_hdr *bcf_hdr, | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 789 | 			   const struct i2400m_bcf_hdr *bcf, size_t offset) | 
 | 790 | { | 
 | 791 | 	int ret = 0; | 
 | 792 | 	struct device *dev = i2400m_dev(i2400m); | 
 | 793 | 	struct i2400m_bootrom_header *cmd, ack; | 
 | 794 | 	struct { | 
 | 795 | 		struct i2400m_bootrom_header cmd; | 
 | 796 | 		u8 cmd_pl[0]; | 
| Eric Dumazet | ba2d358 | 2010-06-02 18:10:09 +0000 | [diff] [blame] | 797 | 	} __packed *cmd_buf; | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 798 | 	size_t signature_block_offset, signature_block_size; | 
 | 799 |  | 
 | 800 | 	d_fnstart(3, dev, "offset %zu\n", offset); | 
 | 801 | 	cmd = (void *) bcf + offset; | 
| Inaky Perez-Gonzalez | 32742e6 | 2009-09-03 15:56:40 -0700 | [diff] [blame] | 802 | 	if (i2400m_boot_is_signed(i2400m) == 0) { | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 803 | 		struct i2400m_bootrom_header jump_ack; | 
| Dirk Brandewie | ead6823 | 2009-05-12 06:22:27 -0700 | [diff] [blame] | 804 | 		d_printf(1, dev, "unsecure boot, jumping to 0x%08x\n", | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 805 | 			le32_to_cpu(cmd->target_addr)); | 
| Cindy H Kao | 8d8fe19 | 2009-08-18 19:27:18 -0700 | [diff] [blame] | 806 | 		cmd_buf = i2400m->bm_cmd_buf; | 
 | 807 | 		memcpy(&cmd_buf->cmd, cmd, sizeof(*cmd)); | 
 | 808 | 		cmd = &cmd_buf->cmd; | 
 | 809 | 		/* now cmd points to the actual bootrom_header in cmd_buf */ | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 810 | 		i2400m_brh_set_opcode(cmd, I2400M_BRH_JUMP); | 
 | 811 | 		cmd->data_size = 0; | 
 | 812 | 		ret = i2400m_bm_cmd(i2400m, cmd, sizeof(*cmd), | 
 | 813 | 				    &jump_ack, sizeof(jump_ack), 0); | 
 | 814 | 	} else { | 
| Dirk Brandewie | ead6823 | 2009-05-12 06:22:27 -0700 | [diff] [blame] | 815 | 		d_printf(1, dev, "secure boot, jumping to 0x%08x\n", | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 816 | 			 le32_to_cpu(cmd->target_addr)); | 
 | 817 | 		cmd_buf = i2400m->bm_cmd_buf; | 
 | 818 | 		memcpy(&cmd_buf->cmd, cmd, sizeof(*cmd)); | 
 | 819 | 		signature_block_offset = | 
| Inaky Perez-Gonzalez | 10607c8 | 2009-09-09 17:11:57 -0700 | [diff] [blame] | 820 | 			sizeof(*bcf_hdr) | 
 | 821 | 			+ le32_to_cpu(bcf_hdr->key_size) * sizeof(u32) | 
 | 822 | 			+ le32_to_cpu(bcf_hdr->exponent_size) * sizeof(u32); | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 823 | 		signature_block_size = | 
| Inaky Perez-Gonzalez | 10607c8 | 2009-09-09 17:11:57 -0700 | [diff] [blame] | 824 | 			le32_to_cpu(bcf_hdr->modulus_size) * sizeof(u32); | 
 | 825 | 		memcpy(cmd_buf->cmd_pl, | 
 | 826 | 		       (void *) bcf_hdr + signature_block_offset, | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 827 | 		       signature_block_size); | 
 | 828 | 		ret = i2400m_bm_cmd(i2400m, &cmd_buf->cmd, | 
 | 829 | 				    sizeof(cmd_buf->cmd) + signature_block_size, | 
 | 830 | 				    &ack, sizeof(ack), I2400M_BM_CMD_RAW); | 
 | 831 | 	} | 
 | 832 | 	d_fnend(3, dev, "returning %d\n", ret); | 
 | 833 | 	return ret; | 
 | 834 | } | 
 | 835 |  | 
 | 836 |  | 
 | 837 | /** | 
 | 838 |  * i2400m_bootrom_init - Reboots a powered device into boot mode | 
 | 839 |  * | 
 | 840 |  * @i2400m: device descriptor | 
 | 841 |  * @flags: | 
| Inaky Perez-Gonzalez | 923d708 | 2009-09-04 14:50:59 -0700 | [diff] [blame] | 842 |  *      I2400M_BRI_SOFT: a reboot barker has been seen | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 843 |  *          already, so don't wait for it. | 
 | 844 |  * | 
 | 845 |  *      I2400M_BRI_NO_REBOOT: Don't send a reboot command, but wait | 
 | 846 |  *          for a reboot barker notification. This is a one shot; if | 
 | 847 |  *          the state machine needs to send a reboot command it will. | 
 | 848 |  * | 
 | 849 |  * Returns: | 
 | 850 |  * | 
 | 851 |  *     < 0 errno code on error, 0 if ok. | 
 | 852 |  * | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 853 |  * Description: | 
 | 854 |  * | 
 | 855 |  * Tries hard enough to put the device in boot-mode. There are two | 
 | 856 |  * main phases to this: | 
 | 857 |  * | 
 | 858 |  * a. (1) send a reboot command and (2) get a reboot barker | 
| Inaky Perez-Gonzalez | 923d708 | 2009-09-04 14:50:59 -0700 | [diff] [blame] | 859 |  * | 
 | 860 |  * b. (1) echo/ack the reboot sending the reboot barker back and (2) | 
 | 861 |  *        getting an ack barker in return | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 862 |  * | 
 | 863 |  * We want to skip (a) in some cases [soft]. The state machine is | 
 | 864 |  * horrible, but it is basically: on each phase, send what has to be | 
 | 865 |  * sent (if any), wait for the answer and act on the answer. We might | 
 | 866 |  * have to backtrack and retry, so we keep a max tries counter for | 
 | 867 |  * that. | 
 | 868 |  * | 
| Inaky Perez-Gonzalez | 923d708 | 2009-09-04 14:50:59 -0700 | [diff] [blame] | 869 |  * It sucks because we don't know ahead of time which is going to be | 
 | 870 |  * the reboot barker (the device might send different ones depending | 
 | 871 |  * on its EEPROM config) and once the device reboots and waits for the | 
 | 872 |  * echo/ack reboot barker being sent back, it doesn't understand | 
 | 873 |  * anything else. So we can be left at the point where we don't know | 
 | 874 |  * what to send to it -- cold reset and bus reset seem to have little | 
 | 875 |  * effect. So the function iterates (in this case) through all the | 
 | 876 |  * known barkers and tries them all until an ACK is | 
 | 877 |  * received. Otherwise, it gives up. | 
 | 878 |  * | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 879 |  * If we get a timeout after sending a warm reset, we do it again. | 
 | 880 |  */ | 
 | 881 | int i2400m_bootrom_init(struct i2400m *i2400m, enum i2400m_bri flags) | 
 | 882 | { | 
 | 883 | 	int result; | 
 | 884 | 	struct device *dev = i2400m_dev(i2400m); | 
 | 885 | 	struct i2400m_bootrom_header *cmd; | 
 | 886 | 	struct i2400m_bootrom_header ack; | 
| Dirk Brandewie | c308365 | 2009-08-13 13:48:29 -0700 | [diff] [blame] | 887 | 	int count = i2400m->bus_bm_retries; | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 888 | 	int ack_timeout_cnt = 1; | 
| Inaky Perez-Gonzalez | 923d708 | 2009-09-04 14:50:59 -0700 | [diff] [blame] | 889 | 	unsigned i; | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 890 |  | 
| Inaky Perez-Gonzalez | aba3792 | 2009-09-03 15:14:29 -0700 | [diff] [blame] | 891 | 	BUILD_BUG_ON(sizeof(*cmd) != sizeof(i2400m_barker_db[0].data)); | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 892 | 	BUILD_BUG_ON(sizeof(ack) != sizeof(i2400m_ACK_BARKER)); | 
 | 893 |  | 
 | 894 | 	d_fnstart(4, dev, "(i2400m %p flags 0x%08x)\n", i2400m, flags); | 
 | 895 | 	result = -ENOMEM; | 
 | 896 | 	cmd = i2400m->bm_cmd_buf; | 
 | 897 | 	if (flags & I2400M_BRI_SOFT) | 
 | 898 | 		goto do_reboot_ack; | 
 | 899 | do_reboot: | 
| Inaky Perez-Gonzalez | 923d708 | 2009-09-04 14:50:59 -0700 | [diff] [blame] | 900 | 	ack_timeout_cnt = 1; | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 901 | 	if (--count < 0) | 
 | 902 | 		goto error_timeout; | 
 | 903 | 	d_printf(4, dev, "device reboot: reboot command [%d # left]\n", | 
 | 904 | 		 count); | 
 | 905 | 	if ((flags & I2400M_BRI_NO_REBOOT) == 0) | 
| Inaky Perez-Gonzalez | c931cee | 2009-10-19 16:24:56 +0900 | [diff] [blame] | 906 | 		i2400m_reset(i2400m, I2400M_RT_WARM); | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 907 | 	result = i2400m_bm_cmd(i2400m, NULL, 0, &ack, sizeof(ack), | 
 | 908 | 			       I2400M_BM_CMD_RAW); | 
 | 909 | 	flags &= ~I2400M_BRI_NO_REBOOT; | 
 | 910 | 	switch (result) { | 
 | 911 | 	case -ERESTARTSYS: | 
| Inaky Perez-Gonzalez | 923d708 | 2009-09-04 14:50:59 -0700 | [diff] [blame] | 912 | 		/* | 
 | 913 | 		 * at this point, i2400m_bm_cmd(), through | 
 | 914 | 		 * __i2400m_bm_ack_process(), has updated | 
 | 915 | 		 * i2400m->barker and we are good to go. | 
 | 916 | 		 */ | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 917 | 		d_printf(4, dev, "device reboot: got reboot barker\n"); | 
 | 918 | 		break; | 
 | 919 | 	case -EISCONN:	/* we don't know how it got here...but we follow it */ | 
 | 920 | 		d_printf(4, dev, "device reboot: got ack barker - whatever\n"); | 
 | 921 | 		goto do_reboot; | 
| Inaky Perez-Gonzalez | 923d708 | 2009-09-04 14:50:59 -0700 | [diff] [blame] | 922 | 	case -ETIMEDOUT: | 
 | 923 | 		/* | 
 | 924 | 		 * Device has timed out, we might be in boot mode | 
 | 925 | 		 * already and expecting an ack; if we don't know what | 
 | 926 | 		 * the barker is, we just send them all. Cold reset | 
 | 927 | 		 * and bus reset don't work. Beats me. | 
 | 928 | 		 */ | 
 | 929 | 		if (i2400m->barker != NULL) { | 
 | 930 | 			dev_err(dev, "device boot: reboot barker timed out, " | 
 | 931 | 				"trying (set) %08x echo/ack\n", | 
 | 932 | 				le32_to_cpu(i2400m->barker->data[0])); | 
| Inaky Perez-Gonzalez | aba3792 | 2009-09-03 15:14:29 -0700 | [diff] [blame] | 933 | 			goto do_reboot_ack; | 
 | 934 | 		} | 
| Inaky Perez-Gonzalez | 923d708 | 2009-09-04 14:50:59 -0700 | [diff] [blame] | 935 | 		for (i = 0; i < i2400m_barker_db_used; i++) { | 
 | 936 | 			struct i2400m_barker_db *barker = &i2400m_barker_db[i]; | 
 | 937 | 			memcpy(cmd, barker->data, sizeof(barker->data)); | 
 | 938 | 			result = i2400m_bm_cmd(i2400m, cmd, sizeof(*cmd), | 
 | 939 | 					       &ack, sizeof(ack), | 
 | 940 | 					       I2400M_BM_CMD_RAW); | 
 | 941 | 			if (result == -EISCONN) { | 
 | 942 | 				dev_warn(dev, "device boot: got ack barker " | 
 | 943 | 					 "after sending echo/ack barker " | 
 | 944 | 					 "#%d/%08x; rebooting j.i.c.\n", | 
 | 945 | 					 i, le32_to_cpu(barker->data[0])); | 
 | 946 | 				flags &= ~I2400M_BRI_NO_REBOOT; | 
 | 947 | 				goto do_reboot; | 
 | 948 | 			} | 
 | 949 | 		} | 
 | 950 | 		dev_err(dev, "device boot: tried all the echo/acks, could " | 
 | 951 | 			"not get device to respond; giving up"); | 
 | 952 | 		result = -ESHUTDOWN; | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 953 | 	case -EPROTO: | 
 | 954 | 	case -ESHUTDOWN:	/* dev is gone */ | 
 | 955 | 	case -EINTR:		/* user cancelled */ | 
 | 956 | 		goto error_dev_gone; | 
 | 957 | 	default: | 
 | 958 | 		dev_err(dev, "device reboot: error %d while waiting " | 
 | 959 | 			"for reboot barker - rebooting\n", result); | 
| Inaky Perez-Gonzalez | 923d708 | 2009-09-04 14:50:59 -0700 | [diff] [blame] | 960 | 		d_dump(1, dev, &ack, result); | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 961 | 		goto do_reboot; | 
 | 962 | 	} | 
 | 963 | 	/* At this point we ack back with 4 REBOOT barkers and expect | 
 | 964 | 	 * 4 ACK barkers. This is ugly, as we send a raw command -- | 
 | 965 | 	 * hence the cast. _bm_cmd() will catch the reboot ack | 
 | 966 | 	 * notification and report it as -EISCONN. */ | 
 | 967 | do_reboot_ack: | 
 | 968 | 	d_printf(4, dev, "device reboot ack: sending ack [%d # left]\n", count); | 
| Inaky Perez-Gonzalez | aba3792 | 2009-09-03 15:14:29 -0700 | [diff] [blame] | 969 | 	memcpy(cmd, i2400m->barker->data, sizeof(i2400m->barker->data)); | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 970 | 	result = i2400m_bm_cmd(i2400m, cmd, sizeof(*cmd), | 
 | 971 | 			       &ack, sizeof(ack), I2400M_BM_CMD_RAW); | 
 | 972 | 	switch (result) { | 
 | 973 | 	case -ERESTARTSYS: | 
 | 974 | 		d_printf(4, dev, "reboot ack: got reboot barker - retrying\n"); | 
 | 975 | 		if (--count < 0) | 
 | 976 | 			goto error_timeout; | 
 | 977 | 		goto do_reboot_ack; | 
 | 978 | 	case -EISCONN: | 
 | 979 | 		d_printf(4, dev, "reboot ack: got ack barker - good\n"); | 
 | 980 | 		break; | 
 | 981 | 	case -ETIMEDOUT:	/* no response, maybe it is the other type? */ | 
| Inaky Perez-Gonzalez | aba3792 | 2009-09-03 15:14:29 -0700 | [diff] [blame] | 982 | 		if (ack_timeout_cnt-- < 0) { | 
 | 983 | 			d_printf(4, dev, "reboot ack timedout: retrying\n"); | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 984 | 			goto do_reboot_ack; | 
 | 985 | 		} else { | 
 | 986 | 			dev_err(dev, "reboot ack timedout too long: " | 
 | 987 | 				"trying reboot\n"); | 
 | 988 | 			goto do_reboot; | 
 | 989 | 		} | 
 | 990 | 		break; | 
 | 991 | 	case -EPROTO: | 
 | 992 | 	case -ESHUTDOWN:	/* dev is gone */ | 
 | 993 | 		goto error_dev_gone; | 
 | 994 | 	default: | 
 | 995 | 		dev_err(dev, "device reboot ack: error %d while waiting for " | 
 | 996 | 			"reboot ack barker - rebooting\n", result); | 
 | 997 | 		goto do_reboot; | 
 | 998 | 	} | 
 | 999 | 	d_printf(2, dev, "device reboot ack: got ack barker - boot done\n"); | 
 | 1000 | 	result = 0; | 
 | 1001 | exit_timeout: | 
 | 1002 | error_dev_gone: | 
 | 1003 | 	d_fnend(4, dev, "(i2400m %p flags 0x%08x) = %d\n", | 
 | 1004 | 		i2400m, flags, result); | 
 | 1005 | 	return result; | 
 | 1006 |  | 
 | 1007 | error_timeout: | 
| Inaky Perez-Gonzalez | 6e053d6 | 2009-06-05 09:31:26 +0800 | [diff] [blame] | 1008 | 	dev_err(dev, "Timed out waiting for reboot ack\n"); | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1009 | 	result = -ETIMEDOUT; | 
 | 1010 | 	goto exit_timeout; | 
 | 1011 | } | 
 | 1012 |  | 
 | 1013 |  | 
 | 1014 | /* | 
 | 1015 |  * Read the MAC addr | 
 | 1016 |  * | 
 | 1017 |  * The position this function reads is fixed in device memory and | 
 | 1018 |  * always available, even without firmware. | 
 | 1019 |  * | 
 | 1020 |  * Note we specify we want to read only six bytes, but provide space | 
 | 1021 |  * for 16, as we always get it rounded up. | 
 | 1022 |  */ | 
 | 1023 | int i2400m_read_mac_addr(struct i2400m *i2400m) | 
 | 1024 | { | 
 | 1025 | 	int result; | 
 | 1026 | 	struct device *dev = i2400m_dev(i2400m); | 
 | 1027 | 	struct net_device *net_dev = i2400m->wimax_dev.net_dev; | 
 | 1028 | 	struct i2400m_bootrom_header *cmd; | 
 | 1029 | 	struct { | 
 | 1030 | 		struct i2400m_bootrom_header ack; | 
 | 1031 | 		u8 ack_pl[16]; | 
| Eric Dumazet | ba2d358 | 2010-06-02 18:10:09 +0000 | [diff] [blame] | 1032 | 	} __packed ack_buf; | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1033 |  | 
 | 1034 | 	d_fnstart(5, dev, "(i2400m %p)\n", i2400m); | 
 | 1035 | 	cmd = i2400m->bm_cmd_buf; | 
 | 1036 | 	cmd->command = i2400m_brh_command(I2400M_BRH_READ, 0, 1); | 
 | 1037 | 	cmd->target_addr = cpu_to_le32(0x00203fe8); | 
 | 1038 | 	cmd->data_size = cpu_to_le32(6); | 
 | 1039 | 	result = i2400m_bm_cmd(i2400m, cmd, sizeof(*cmd), | 
 | 1040 | 			       &ack_buf.ack, sizeof(ack_buf), 0); | 
 | 1041 | 	if (result < 0) { | 
 | 1042 | 		dev_err(dev, "BM: read mac addr failed: %d\n", result); | 
 | 1043 | 		goto error_read_mac; | 
 | 1044 | 	} | 
| hartleys | 595acf2 | 2010-01-05 07:01:54 +0000 | [diff] [blame] | 1045 | 	d_printf(2, dev, "mac addr is %pM\n", ack_buf.ack_pl); | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1046 | 	if (i2400m->bus_bm_mac_addr_impaired == 1) { | 
 | 1047 | 		ack_buf.ack_pl[0] = 0x00; | 
 | 1048 | 		ack_buf.ack_pl[1] = 0x16; | 
 | 1049 | 		ack_buf.ack_pl[2] = 0xd3; | 
 | 1050 | 		get_random_bytes(&ack_buf.ack_pl[3], 3); | 
 | 1051 | 		dev_err(dev, "BM is MAC addr impaired, faking MAC addr to " | 
| hartleys | 595acf2 | 2010-01-05 07:01:54 +0000 | [diff] [blame] | 1052 | 			"mac addr is %pM\n", ack_buf.ack_pl); | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1053 | 		result = 0; | 
 | 1054 | 	} | 
 | 1055 | 	net_dev->addr_len = ETH_ALEN; | 
 | 1056 | 	memcpy(net_dev->perm_addr, ack_buf.ack_pl, ETH_ALEN); | 
 | 1057 | 	memcpy(net_dev->dev_addr, ack_buf.ack_pl, ETH_ALEN); | 
 | 1058 | error_read_mac: | 
 | 1059 | 	d_fnend(5, dev, "(i2400m %p) = %d\n", i2400m, result); | 
 | 1060 | 	return result; | 
 | 1061 | } | 
 | 1062 |  | 
 | 1063 |  | 
 | 1064 | /* | 
 | 1065 |  * Initialize a non signed boot | 
 | 1066 |  * | 
 | 1067 |  * This implies sending some magic values to the device's memory. Note | 
 | 1068 |  * we convert the values to little endian in the same array | 
 | 1069 |  * declaration. | 
 | 1070 |  */ | 
 | 1071 | static | 
 | 1072 | int i2400m_dnload_init_nonsigned(struct i2400m *i2400m) | 
 | 1073 | { | 
| Dirk Brandewie | 7308a0c | 2009-05-21 11:56:34 -0700 | [diff] [blame] | 1074 | 	unsigned i = 0; | 
 | 1075 | 	int ret = 0; | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1076 | 	struct device *dev = i2400m_dev(i2400m); | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1077 | 	d_fnstart(5, dev, "(i2400m %p)\n", i2400m); | 
| Dirk Brandewie | 7308a0c | 2009-05-21 11:56:34 -0700 | [diff] [blame] | 1078 | 	if (i2400m->bus_bm_pokes_table) { | 
 | 1079 | 		while (i2400m->bus_bm_pokes_table[i].address) { | 
 | 1080 | 			ret = i2400m_download_chunk( | 
 | 1081 | 				i2400m, | 
 | 1082 | 				&i2400m->bus_bm_pokes_table[i].data, | 
 | 1083 | 				sizeof(i2400m->bus_bm_pokes_table[i].data), | 
 | 1084 | 				i2400m->bus_bm_pokes_table[i].address, 1, 1); | 
 | 1085 | 			if (ret < 0) | 
 | 1086 | 				break; | 
 | 1087 | 			i++; | 
 | 1088 | 		} | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1089 | 	} | 
 | 1090 | 	d_fnend(5, dev, "(i2400m %p) = %d\n", i2400m, ret); | 
 | 1091 | 	return ret; | 
 | 1092 | } | 
 | 1093 |  | 
 | 1094 |  | 
 | 1095 | /* | 
 | 1096 |  * Initialize the signed boot process | 
 | 1097 |  * | 
 | 1098 |  * @i2400m: device descriptor | 
 | 1099 |  * | 
 | 1100 |  * @bcf_hdr: pointer to the firmware header; assumes it is fully in | 
 | 1101 |  *     memory (it has gone through basic validation). | 
 | 1102 |  * | 
 | 1103 |  * Returns: 0 if ok, < 0 errno code on error, -ERESTARTSYS if the hw | 
 | 1104 |  *     rebooted. | 
 | 1105 |  * | 
 | 1106 |  * This writes the firmware BCF header to the device using the | 
 | 1107 |  * HASH_PAYLOAD_ONLY command. | 
 | 1108 |  */ | 
 | 1109 | static | 
 | 1110 | int i2400m_dnload_init_signed(struct i2400m *i2400m, | 
 | 1111 | 			      const struct i2400m_bcf_hdr *bcf_hdr) | 
 | 1112 | { | 
 | 1113 | 	int ret; | 
 | 1114 | 	struct device *dev = i2400m_dev(i2400m); | 
 | 1115 | 	struct { | 
 | 1116 | 		struct i2400m_bootrom_header cmd; | 
 | 1117 | 		struct i2400m_bcf_hdr cmd_pl; | 
| Eric Dumazet | ba2d358 | 2010-06-02 18:10:09 +0000 | [diff] [blame] | 1118 | 	} __packed *cmd_buf; | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1119 | 	struct i2400m_bootrom_header ack; | 
 | 1120 |  | 
 | 1121 | 	d_fnstart(5, dev, "(i2400m %p bcf_hdr %p)\n", i2400m, bcf_hdr); | 
 | 1122 | 	cmd_buf = i2400m->bm_cmd_buf; | 
 | 1123 | 	cmd_buf->cmd.command = | 
 | 1124 | 		i2400m_brh_command(I2400M_BRH_HASH_PAYLOAD_ONLY, 0, 0); | 
 | 1125 | 	cmd_buf->cmd.target_addr = 0; | 
 | 1126 | 	cmd_buf->cmd.data_size = cpu_to_le32(sizeof(cmd_buf->cmd_pl)); | 
 | 1127 | 	memcpy(&cmd_buf->cmd_pl, bcf_hdr, sizeof(*bcf_hdr)); | 
 | 1128 | 	ret = i2400m_bm_cmd(i2400m, &cmd_buf->cmd, sizeof(*cmd_buf), | 
 | 1129 | 			    &ack, sizeof(ack), 0); | 
 | 1130 | 	if (ret >= 0) | 
 | 1131 | 		ret = 0; | 
 | 1132 | 	d_fnend(5, dev, "(i2400m %p bcf_hdr %p) = %d\n", i2400m, bcf_hdr, ret); | 
 | 1133 | 	return ret; | 
 | 1134 | } | 
 | 1135 |  | 
 | 1136 |  | 
 | 1137 | /* | 
 | 1138 |  * Initialize the firmware download at the device size | 
 | 1139 |  * | 
 | 1140 |  * Multiplex to the one that matters based on the device's mode | 
 | 1141 |  * (signed or non-signed). | 
 | 1142 |  */ | 
 | 1143 | static | 
| Inaky Perez-Gonzalez | 10607c8 | 2009-09-09 17:11:57 -0700 | [diff] [blame] | 1144 | int i2400m_dnload_init(struct i2400m *i2400m, | 
 | 1145 | 		       const struct i2400m_bcf_hdr *bcf_hdr) | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1146 | { | 
 | 1147 | 	int result; | 
 | 1148 | 	struct device *dev = i2400m_dev(i2400m); | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1149 |  | 
| Inaky Perez-Gonzalez | 32742e6 | 2009-09-03 15:56:40 -0700 | [diff] [blame] | 1150 | 	if (i2400m_boot_is_signed(i2400m)) { | 
 | 1151 | 		d_printf(1, dev, "signed boot\n"); | 
| Inaky Perez-Gonzalez | 10607c8 | 2009-09-09 17:11:57 -0700 | [diff] [blame] | 1152 | 		result = i2400m_dnload_init_signed(i2400m, bcf_hdr); | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1153 | 		if (result == -ERESTARTSYS) | 
 | 1154 | 			return result; | 
 | 1155 | 		if (result < 0) | 
| Inaky Perez-Gonzalez | 32742e6 | 2009-09-03 15:56:40 -0700 | [diff] [blame] | 1156 | 			dev_err(dev, "firmware %s: signed boot download " | 
 | 1157 | 				"initialization failed: %d\n", | 
 | 1158 | 				i2400m->fw_name, result); | 
 | 1159 | 	} else { | 
 | 1160 | 		/* non-signed boot process without pokes */ | 
 | 1161 | 		d_printf(1, dev, "non-signed boot\n"); | 
 | 1162 | 		result = i2400m_dnload_init_nonsigned(i2400m); | 
 | 1163 | 		if (result == -ERESTARTSYS) | 
 | 1164 | 			return result; | 
 | 1165 | 		if (result < 0) | 
 | 1166 | 			dev_err(dev, "firmware %s: non-signed download " | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1167 | 				"initialization failed: %d\n", | 
| Inaky Perez-Gonzalez | 1039abb | 2009-02-28 23:42:47 +0000 | [diff] [blame] | 1168 | 				i2400m->fw_name, result); | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1169 | 	} | 
 | 1170 | 	return result; | 
 | 1171 | } | 
 | 1172 |  | 
 | 1173 |  | 
 | 1174 | /* | 
| Inaky Perez-Gonzalez | bfc4418 | 2009-09-04 17:07:21 -0700 | [diff] [blame] | 1175 |  * Run consistency tests on the firmware file and load up headers | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1176 |  * | 
 | 1177 |  * Check for the firmware being made for the i2400m device, | 
 | 1178 |  * etc...These checks are mostly informative, as the device will make | 
 | 1179 |  * them too; but the driver's response is more informative on what | 
 | 1180 |  * went wrong. | 
| Inaky Perez-Gonzalez | bfc4418 | 2009-09-04 17:07:21 -0700 | [diff] [blame] | 1181 |  * | 
 | 1182 |  * This will also look at all the headers present on the firmware | 
 | 1183 |  * file, and update i2400m->fw_bcf_hdr to point to them. | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1184 |  */ | 
 | 1185 | static | 
| Inaky Perez-Gonzalez | bfc4418 | 2009-09-04 17:07:21 -0700 | [diff] [blame] | 1186 | int i2400m_fw_hdr_check(struct i2400m *i2400m, | 
 | 1187 | 			const struct i2400m_bcf_hdr *bcf_hdr, | 
 | 1188 | 			size_t index, size_t offset) | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1189 | { | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1190 | 	struct device *dev = i2400m_dev(i2400m); | 
| Inaky Perez-Gonzalez | bfc4418 | 2009-09-04 17:07:21 -0700 | [diff] [blame] | 1191 |  | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1192 | 	unsigned module_type, header_len, major_version, minor_version, | 
 | 1193 | 		module_id, module_vendor, date, size; | 
 | 1194 |  | 
| Inaky Perez-Gonzalez | a385a53 | 2010-06-11 11:51:20 -0700 | [diff] [blame] | 1195 | 	module_type = le32_to_cpu(bcf_hdr->module_type); | 
| Inaky Perez-Gonzalez | bfc4418 | 2009-09-04 17:07:21 -0700 | [diff] [blame] | 1196 | 	header_len = sizeof(u32) * le32_to_cpu(bcf_hdr->header_len); | 
 | 1197 | 	major_version = (le32_to_cpu(bcf_hdr->header_version) & 0xffff0000) | 
 | 1198 | 		>> 16; | 
 | 1199 | 	minor_version = le32_to_cpu(bcf_hdr->header_version) & 0x0000ffff; | 
 | 1200 | 	module_id = le32_to_cpu(bcf_hdr->module_id); | 
 | 1201 | 	module_vendor = le32_to_cpu(bcf_hdr->module_vendor); | 
 | 1202 | 	date = le32_to_cpu(bcf_hdr->date); | 
 | 1203 | 	size = sizeof(u32) * le32_to_cpu(bcf_hdr->size); | 
 | 1204 |  | 
| Inaky Perez-Gonzalez | 10607c8 | 2009-09-09 17:11:57 -0700 | [diff] [blame] | 1205 | 	d_printf(1, dev, "firmware %s #%zd@%08zx: BCF header " | 
 | 1206 | 		 "type:vendor:id 0x%x:%x:%x v%u.%u (%u/%u B) built %08x\n", | 
| Inaky Perez-Gonzalez | bfc4418 | 2009-09-04 17:07:21 -0700 | [diff] [blame] | 1207 | 		 i2400m->fw_name, index, offset, | 
 | 1208 | 		 module_type, module_vendor, module_id, | 
 | 1209 | 		 major_version, minor_version, header_len, size, date); | 
 | 1210 |  | 
 | 1211 | 	/* Hard errors */ | 
 | 1212 | 	if (major_version != 1) { | 
| Inaky Perez-Gonzalez | 10607c8 | 2009-09-09 17:11:57 -0700 | [diff] [blame] | 1213 | 		dev_err(dev, "firmware %s #%zd@%08zx: major header version " | 
| Inaky Perez-Gonzalez | bfc4418 | 2009-09-04 17:07:21 -0700 | [diff] [blame] | 1214 | 			"v%u.%u not supported\n", | 
 | 1215 | 			i2400m->fw_name, index, offset, | 
 | 1216 | 			major_version, minor_version); | 
 | 1217 | 		return -EBADF; | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1218 | 	} | 
 | 1219 |  | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1220 | 	if (module_type != 6) {		/* built for the right hardware? */ | 
| Inaky Perez-Gonzalez | 10607c8 | 2009-09-09 17:11:57 -0700 | [diff] [blame] | 1221 | 		dev_err(dev, "firmware %s #%zd@%08zx: unexpected module " | 
| Inaky Perez-Gonzalez | bfc4418 | 2009-09-04 17:07:21 -0700 | [diff] [blame] | 1222 | 			"type 0x%x; aborting\n", | 
 | 1223 | 			i2400m->fw_name, index, offset, | 
 | 1224 | 			module_type); | 
 | 1225 | 		return -EBADF; | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1226 | 	} | 
 | 1227 |  | 
| Inaky Perez-Gonzalez | bfc4418 | 2009-09-04 17:07:21 -0700 | [diff] [blame] | 1228 | 	if (module_vendor != 0x8086) { | 
| Inaky Perez-Gonzalez | 10607c8 | 2009-09-09 17:11:57 -0700 | [diff] [blame] | 1229 | 		dev_err(dev, "firmware %s #%zd@%08zx: unexpected module " | 
| Inaky Perez-Gonzalez | bfc4418 | 2009-09-04 17:07:21 -0700 | [diff] [blame] | 1230 | 			"vendor 0x%x; aborting\n", | 
 | 1231 | 			i2400m->fw_name, index, offset, module_vendor); | 
 | 1232 | 		return -EBADF; | 
| Inaky Perez-Gonzalez | fabce1a | 2009-09-04 14:53:43 -0700 | [diff] [blame] | 1233 | 	} | 
 | 1234 |  | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1235 | 	if (date < 0x20080300) | 
| Inaky Perez-Gonzalez | 10607c8 | 2009-09-09 17:11:57 -0700 | [diff] [blame] | 1236 | 		dev_warn(dev, "firmware %s #%zd@%08zx: build date %08x " | 
| Inaky Perez-Gonzalez | bfc4418 | 2009-09-04 17:07:21 -0700 | [diff] [blame] | 1237 | 			 "too old; unsupported\n", | 
 | 1238 | 			 i2400m->fw_name, index, offset, date); | 
 | 1239 | 	return 0; | 
 | 1240 | } | 
 | 1241 |  | 
 | 1242 |  | 
 | 1243 | /* | 
 | 1244 |  * Run consistency tests on the firmware file and load up headers | 
 | 1245 |  * | 
 | 1246 |  * Check for the firmware being made for the i2400m device, | 
 | 1247 |  * etc...These checks are mostly informative, as the device will make | 
 | 1248 |  * them too; but the driver's response is more informative on what | 
 | 1249 |  * went wrong. | 
 | 1250 |  * | 
 | 1251 |  * This will also look at all the headers present on the firmware | 
 | 1252 |  * file, and update i2400m->fw_hdrs to point to them. | 
 | 1253 |  */ | 
 | 1254 | static | 
 | 1255 | int i2400m_fw_check(struct i2400m *i2400m, const void *bcf, size_t bcf_size) | 
 | 1256 | { | 
 | 1257 | 	int result; | 
 | 1258 | 	struct device *dev = i2400m_dev(i2400m); | 
 | 1259 | 	size_t headers = 0; | 
 | 1260 | 	const struct i2400m_bcf_hdr *bcf_hdr; | 
 | 1261 | 	const void *itr, *next, *top; | 
| Inaky Perez-Gonzalez | 10607c8 | 2009-09-09 17:11:57 -0700 | [diff] [blame] | 1262 | 	size_t slots = 0, used_slots = 0; | 
| Inaky Perez-Gonzalez | bfc4418 | 2009-09-04 17:07:21 -0700 | [diff] [blame] | 1263 |  | 
 | 1264 | 	for (itr = bcf, top = itr + bcf_size; | 
 | 1265 | 	     itr < top; | 
 | 1266 | 	     headers++, itr = next) { | 
 | 1267 | 		size_t leftover, offset, header_len, size; | 
 | 1268 |  | 
 | 1269 | 		leftover = top - itr; | 
 | 1270 | 		offset = itr - (const void *) bcf; | 
 | 1271 | 		if (leftover <= sizeof(*bcf_hdr)) { | 
| Inaky Perez-Gonzalez | 10607c8 | 2009-09-09 17:11:57 -0700 | [diff] [blame] | 1272 | 			dev_err(dev, "firmware %s: %zu B left at @%zx, " | 
| Inaky Perez-Gonzalez | bfc4418 | 2009-09-04 17:07:21 -0700 | [diff] [blame] | 1273 | 				"not enough for BCF header\n", | 
 | 1274 | 				i2400m->fw_name, leftover, offset); | 
 | 1275 | 			break; | 
 | 1276 | 		} | 
 | 1277 | 		bcf_hdr = itr; | 
 | 1278 | 		/* Only the first header is supposed to be followed by | 
 | 1279 | 		 * payload */ | 
 | 1280 | 		header_len = sizeof(u32) * le32_to_cpu(bcf_hdr->header_len); | 
 | 1281 | 		size = sizeof(u32) * le32_to_cpu(bcf_hdr->size); | 
 | 1282 | 		if (headers == 0) | 
 | 1283 | 			next = itr + size; | 
 | 1284 | 		else | 
 | 1285 | 			next = itr + header_len; | 
 | 1286 |  | 
 | 1287 | 		result = i2400m_fw_hdr_check(i2400m, bcf_hdr, headers, offset); | 
 | 1288 | 		if (result < 0) | 
 | 1289 | 			continue; | 
 | 1290 | 		if (used_slots + 1 >= slots) { | 
 | 1291 | 			/* +1 -> we need to account for the one we'll | 
 | 1292 | 			 * occupy and at least an extra one for | 
 | 1293 | 			 * always being NULL */ | 
 | 1294 | 			result = i2400m_zrealloc_2x( | 
 | 1295 | 				(void **) &i2400m->fw_hdrs, &slots, | 
 | 1296 | 				sizeof(i2400m->fw_hdrs[0]), | 
 | 1297 | 				GFP_KERNEL); | 
 | 1298 | 			if (result < 0) | 
 | 1299 | 				goto error_zrealloc; | 
 | 1300 | 		} | 
 | 1301 | 		i2400m->fw_hdrs[used_slots] = bcf_hdr; | 
 | 1302 | 		used_slots++; | 
 | 1303 | 	} | 
 | 1304 | 	if (headers == 0) { | 
 | 1305 | 		dev_err(dev, "firmware %s: no usable headers found\n", | 
 | 1306 | 			i2400m->fw_name); | 
 | 1307 | 		result = -EBADF; | 
 | 1308 | 	} else | 
 | 1309 | 		result = 0; | 
 | 1310 | error_zrealloc: | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1311 | 	return result; | 
 | 1312 | } | 
 | 1313 |  | 
 | 1314 |  | 
 | 1315 | /* | 
| Inaky Perez-Gonzalez | 10607c8 | 2009-09-09 17:11:57 -0700 | [diff] [blame] | 1316 |  * Match a barker to a BCF header module ID | 
 | 1317 |  * | 
 | 1318 |  * The device sends a barker which tells the firmware loader which | 
 | 1319 |  * header in the BCF file has to be used. This does the matching. | 
 | 1320 |  */ | 
 | 1321 | static | 
 | 1322 | unsigned i2400m_bcf_hdr_match(struct i2400m *i2400m, | 
 | 1323 | 			      const struct i2400m_bcf_hdr *bcf_hdr) | 
 | 1324 | { | 
 | 1325 | 	u32 barker = le32_to_cpu(i2400m->barker->data[0]) | 
 | 1326 | 		& 0x7fffffff; | 
 | 1327 | 	u32 module_id = le32_to_cpu(bcf_hdr->module_id) | 
 | 1328 | 		& 0x7fffffff;	/* high bit used for something else */ | 
 | 1329 |  | 
 | 1330 | 	/* special case for 5x50 */ | 
 | 1331 | 	if (barker == I2400M_SBOOT_BARKER && module_id == 0) | 
 | 1332 | 		return 1; | 
 | 1333 | 	if (module_id == barker) | 
 | 1334 | 		return 1; | 
 | 1335 | 	return 0; | 
 | 1336 | } | 
 | 1337 |  | 
 | 1338 | static | 
 | 1339 | const struct i2400m_bcf_hdr *i2400m_bcf_hdr_find(struct i2400m *i2400m) | 
 | 1340 | { | 
 | 1341 | 	struct device *dev = i2400m_dev(i2400m); | 
 | 1342 | 	const struct i2400m_bcf_hdr **bcf_itr, *bcf_hdr; | 
 | 1343 | 	unsigned i = 0; | 
 | 1344 | 	u32 barker = le32_to_cpu(i2400m->barker->data[0]); | 
 | 1345 |  | 
 | 1346 | 	d_printf(2, dev, "finding BCF header for barker %08x\n", barker); | 
 | 1347 | 	if (barker == I2400M_NBOOT_BARKER) { | 
 | 1348 | 		bcf_hdr = i2400m->fw_hdrs[0]; | 
 | 1349 | 		d_printf(1, dev, "using BCF header #%u/%08x for non-signed " | 
 | 1350 | 			 "barker\n", 0, le32_to_cpu(bcf_hdr->module_id)); | 
 | 1351 | 		return bcf_hdr; | 
 | 1352 | 	} | 
 | 1353 | 	for (bcf_itr = i2400m->fw_hdrs; *bcf_itr != NULL; bcf_itr++, i++) { | 
 | 1354 | 		bcf_hdr = *bcf_itr; | 
 | 1355 | 		if (i2400m_bcf_hdr_match(i2400m, bcf_hdr)) { | 
 | 1356 | 			d_printf(1, dev, "hit on BCF hdr #%u/%08x\n", | 
 | 1357 | 				 i, le32_to_cpu(bcf_hdr->module_id)); | 
 | 1358 | 			return bcf_hdr; | 
 | 1359 | 		} else | 
 | 1360 | 			d_printf(1, dev, "miss on BCF hdr #%u/%08x\n", | 
 | 1361 | 				 i, le32_to_cpu(bcf_hdr->module_id)); | 
 | 1362 | 	} | 
 | 1363 | 	dev_err(dev, "cannot find a matching BCF header for barker %08x\n", | 
 | 1364 | 		barker); | 
 | 1365 | 	return NULL; | 
 | 1366 | } | 
 | 1367 |  | 
 | 1368 |  | 
 | 1369 | /* | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1370 |  * Download the firmware to the device | 
 | 1371 |  * | 
 | 1372 |  * @i2400m: device descriptor | 
 | 1373 |  * @bcf: pointer to loaded (and minimally verified for consistency) | 
 | 1374 |  *    firmware | 
 | 1375 |  * @bcf_size: size of the @bcf buffer (header plus payloads) | 
 | 1376 |  * | 
 | 1377 |  * The process for doing this is described in this file's header. | 
 | 1378 |  * | 
 | 1379 |  * Note we only reinitialize boot-mode if the flags say so. Some hw | 
 | 1380 |  * iterations need it, some don't. In any case, if we loop, we always | 
 | 1381 |  * need to reinitialize the boot room, hence the flags modification. | 
 | 1382 |  */ | 
 | 1383 | static | 
 | 1384 | int i2400m_fw_dnload(struct i2400m *i2400m, const struct i2400m_bcf_hdr *bcf, | 
| Cindy H Kao | 28cff50 | 2009-10-02 19:31:17 -0700 | [diff] [blame] | 1385 | 		     size_t fw_size, enum i2400m_bri flags) | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1386 | { | 
 | 1387 | 	int ret = 0; | 
 | 1388 | 	struct device *dev = i2400m_dev(i2400m); | 
| Inaky Perez-Gonzalez | ecddfd5 | 2009-06-03 16:13:14 +0800 | [diff] [blame] | 1389 | 	int count = i2400m->bus_bm_retries; | 
| Inaky Perez-Gonzalez | 10607c8 | 2009-09-09 17:11:57 -0700 | [diff] [blame] | 1390 | 	const struct i2400m_bcf_hdr *bcf_hdr; | 
| Cindy H Kao | 28cff50 | 2009-10-02 19:31:17 -0700 | [diff] [blame] | 1391 | 	size_t bcf_size; | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1392 |  | 
| Cindy H Kao | 28cff50 | 2009-10-02 19:31:17 -0700 | [diff] [blame] | 1393 | 	d_fnstart(5, dev, "(i2400m %p bcf %p fw size %zu)\n", | 
 | 1394 | 		  i2400m, bcf, fw_size); | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1395 | 	i2400m->boot_mode = 1; | 
| Inaky Perez-Gonzalez | b4013f9 | 2009-06-03 09:45:55 +0800 | [diff] [blame] | 1396 | 	wmb();		/* Make sure other readers see it */ | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1397 | hw_reboot: | 
 | 1398 | 	if (count-- == 0) { | 
 | 1399 | 		ret = -ERESTARTSYS; | 
 | 1400 | 		dev_err(dev, "device rebooted too many times, aborting\n"); | 
 | 1401 | 		goto error_too_many_reboots; | 
 | 1402 | 	} | 
 | 1403 | 	if (flags & I2400M_BRI_MAC_REINIT) { | 
 | 1404 | 		ret = i2400m_bootrom_init(i2400m, flags); | 
 | 1405 | 		if (ret < 0) { | 
 | 1406 | 			dev_err(dev, "bootrom init failed: %d\n", ret); | 
 | 1407 | 			goto error_bootrom_init; | 
 | 1408 | 		} | 
 | 1409 | 	} | 
 | 1410 | 	flags |= I2400M_BRI_MAC_REINIT; | 
 | 1411 |  | 
 | 1412 | 	/* | 
 | 1413 | 	 * Initialize the download, push the bytes to the device and | 
 | 1414 | 	 * then jump to the new firmware. Note @ret is passed with the | 
 | 1415 | 	 * offset of the jump instruction to _dnload_finalize() | 
| Inaky Perez-Gonzalez | 10607c8 | 2009-09-09 17:11:57 -0700 | [diff] [blame] | 1416 | 	 * | 
 | 1417 | 	 * Note we need to use the BCF header in the firmware image | 
 | 1418 | 	 * that matches the barker that the device sent when it | 
 | 1419 | 	 * rebooted, so it has to be passed along. | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1420 | 	 */ | 
| Inaky Perez-Gonzalez | 10607c8 | 2009-09-09 17:11:57 -0700 | [diff] [blame] | 1421 | 	ret = -EBADF; | 
 | 1422 | 	bcf_hdr = i2400m_bcf_hdr_find(i2400m); | 
 | 1423 | 	if (bcf_hdr == NULL) | 
 | 1424 | 		goto error_bcf_hdr_find; | 
 | 1425 |  | 
 | 1426 | 	ret = i2400m_dnload_init(i2400m, bcf_hdr); | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1427 | 	if (ret == -ERESTARTSYS) | 
 | 1428 | 		goto error_dev_rebooted; | 
 | 1429 | 	if (ret < 0) | 
 | 1430 | 		goto error_dnload_init; | 
 | 1431 |  | 
| Cindy H Kao | 28cff50 | 2009-10-02 19:31:17 -0700 | [diff] [blame] | 1432 | 	/* | 
 | 1433 | 	 * bcf_size refers to one header size plus the fw sections size | 
 | 1434 | 	 * indicated by the header,ie. if there are other extended headers | 
 | 1435 | 	 * at the tail, they are not counted | 
 | 1436 | 	 */ | 
 | 1437 | 	bcf_size = sizeof(u32) * le32_to_cpu(bcf_hdr->size); | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1438 | 	ret = i2400m_dnload_bcf(i2400m, bcf, bcf_size); | 
 | 1439 | 	if (ret == -ERESTARTSYS) | 
 | 1440 | 		goto error_dev_rebooted; | 
 | 1441 | 	if (ret < 0) { | 
 | 1442 | 		dev_err(dev, "fw %s: download failed: %d\n", | 
| Inaky Perez-Gonzalez | 1039abb | 2009-02-28 23:42:47 +0000 | [diff] [blame] | 1443 | 			i2400m->fw_name, ret); | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1444 | 		goto error_dnload_bcf; | 
 | 1445 | 	} | 
 | 1446 |  | 
| Inaky Perez-Gonzalez | 10607c8 | 2009-09-09 17:11:57 -0700 | [diff] [blame] | 1447 | 	ret = i2400m_dnload_finalize(i2400m, bcf_hdr, bcf, ret); | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1448 | 	if (ret == -ERESTARTSYS) | 
 | 1449 | 		goto error_dev_rebooted; | 
 | 1450 | 	if (ret < 0) { | 
 | 1451 | 		dev_err(dev, "fw %s: " | 
 | 1452 | 			"download finalization failed: %d\n", | 
| Inaky Perez-Gonzalez | 1039abb | 2009-02-28 23:42:47 +0000 | [diff] [blame] | 1453 | 			i2400m->fw_name, ret); | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1454 | 		goto error_dnload_finalize; | 
 | 1455 | 	} | 
 | 1456 |  | 
 | 1457 | 	d_printf(2, dev, "fw %s successfully uploaded\n", | 
| Inaky Perez-Gonzalez | 1039abb | 2009-02-28 23:42:47 +0000 | [diff] [blame] | 1458 | 		 i2400m->fw_name); | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1459 | 	i2400m->boot_mode = 0; | 
| Inaky Perez-Gonzalez | b4013f9 | 2009-06-03 09:45:55 +0800 | [diff] [blame] | 1460 | 	wmb();		/* Make sure i2400m_msg_to_dev() sees boot_mode */ | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1461 | error_dnload_finalize: | 
 | 1462 | error_dnload_bcf: | 
 | 1463 | error_dnload_init: | 
| Inaky Perez-Gonzalez | 10607c8 | 2009-09-09 17:11:57 -0700 | [diff] [blame] | 1464 | error_bcf_hdr_find: | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1465 | error_bootrom_init: | 
 | 1466 | error_too_many_reboots: | 
 | 1467 | 	d_fnend(5, dev, "(i2400m %p bcf %p size %zu) = %d\n", | 
| Cindy H Kao | 28cff50 | 2009-10-02 19:31:17 -0700 | [diff] [blame] | 1468 | 		i2400m, bcf, fw_size, ret); | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1469 | 	return ret; | 
 | 1470 |  | 
 | 1471 | error_dev_rebooted: | 
 | 1472 | 	dev_err(dev, "device rebooted, %d tries left\n", count); | 
 | 1473 | 	/* we got the notification already, no need to wait for it again */ | 
 | 1474 | 	flags |= I2400M_BRI_SOFT; | 
 | 1475 | 	goto hw_reboot; | 
 | 1476 | } | 
 | 1477 |  | 
| Inaky Perez-Gonzalez | 7b43ca7 | 2009-09-14 14:10:16 -0700 | [diff] [blame] | 1478 | static | 
 | 1479 | int i2400m_fw_bootstrap(struct i2400m *i2400m, const struct firmware *fw, | 
 | 1480 | 			enum i2400m_bri flags) | 
 | 1481 | { | 
 | 1482 | 	int ret; | 
 | 1483 | 	struct device *dev = i2400m_dev(i2400m); | 
 | 1484 | 	const struct i2400m_bcf_hdr *bcf;	/* Firmware data */ | 
 | 1485 |  | 
 | 1486 | 	d_fnstart(5, dev, "(i2400m %p)\n", i2400m); | 
 | 1487 | 	bcf = (void *) fw->data; | 
 | 1488 | 	ret = i2400m_fw_check(i2400m, bcf, fw->size); | 
 | 1489 | 	if (ret >= 0) | 
 | 1490 | 		ret = i2400m_fw_dnload(i2400m, bcf, fw->size, flags); | 
 | 1491 | 	if (ret < 0) | 
 | 1492 | 		dev_err(dev, "%s: cannot use: %d, skipping\n", | 
 | 1493 | 			i2400m->fw_name, ret); | 
 | 1494 | 	kfree(i2400m->fw_hdrs); | 
 | 1495 | 	i2400m->fw_hdrs = NULL; | 
 | 1496 | 	d_fnend(5, dev, "(i2400m %p) = %d\n", i2400m, ret); | 
 | 1497 | 	return ret; | 
 | 1498 | } | 
 | 1499 |  | 
 | 1500 |  | 
 | 1501 | /* Refcounted container for firmware data */ | 
 | 1502 | struct i2400m_fw { | 
 | 1503 | 	struct kref kref; | 
 | 1504 | 	const struct firmware *fw; | 
 | 1505 | }; | 
 | 1506 |  | 
 | 1507 |  | 
 | 1508 | static | 
 | 1509 | void i2400m_fw_destroy(struct kref *kref) | 
 | 1510 | { | 
 | 1511 | 	struct i2400m_fw *i2400m_fw = | 
 | 1512 | 		container_of(kref, struct i2400m_fw, kref); | 
 | 1513 | 	release_firmware(i2400m_fw->fw); | 
 | 1514 | 	kfree(i2400m_fw); | 
 | 1515 | } | 
 | 1516 |  | 
 | 1517 |  | 
 | 1518 | static | 
 | 1519 | struct i2400m_fw *i2400m_fw_get(struct i2400m_fw *i2400m_fw) | 
 | 1520 | { | 
 | 1521 | 	if (i2400m_fw != NULL && i2400m_fw != (void *) ~0) | 
 | 1522 | 		kref_get(&i2400m_fw->kref); | 
 | 1523 | 	return i2400m_fw; | 
 | 1524 | } | 
 | 1525 |  | 
 | 1526 |  | 
 | 1527 | static | 
 | 1528 | void i2400m_fw_put(struct i2400m_fw *i2400m_fw) | 
 | 1529 | { | 
 | 1530 | 	kref_put(&i2400m_fw->kref, i2400m_fw_destroy); | 
 | 1531 | } | 
 | 1532 |  | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1533 |  | 
 | 1534 | /** | 
 | 1535 |  * i2400m_dev_bootstrap - Bring the device to a known state and upload firmware | 
 | 1536 |  * | 
 | 1537 |  * @i2400m: device descriptor | 
 | 1538 |  * | 
 | 1539 |  * Returns: >= 0 if ok, < 0 errno code on error. | 
 | 1540 |  * | 
 | 1541 |  * This sets up the firmware upload environment, loads the firmware | 
 | 1542 |  * file from disk, verifies and then calls the firmware upload process | 
 | 1543 |  * per se. | 
 | 1544 |  * | 
 | 1545 |  * Can be called either from probe, or after a warm reset.  Can not be | 
 | 1546 |  * called from within an interrupt.  All the flow in this code is | 
 | 1547 |  * single-threade; all I/Os are synchronous. | 
 | 1548 |  */ | 
 | 1549 | int i2400m_dev_bootstrap(struct i2400m *i2400m, enum i2400m_bri flags) | 
 | 1550 | { | 
| Inaky Perez-Gonzalez | ebc5f62 | 2009-09-03 15:53:30 -0700 | [diff] [blame] | 1551 | 	int ret, itr; | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1552 | 	struct device *dev = i2400m_dev(i2400m); | 
| Inaky Perez-Gonzalez | 7b43ca7 | 2009-09-14 14:10:16 -0700 | [diff] [blame] | 1553 | 	struct i2400m_fw *i2400m_fw; | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1554 | 	const struct i2400m_bcf_hdr *bcf;	/* Firmware data */ | 
| Inaky Perez-Gonzalez | 7b43ca7 | 2009-09-14 14:10:16 -0700 | [diff] [blame] | 1555 | 	const struct firmware *fw; | 
| Inaky Perez-Gonzalez | 1039abb | 2009-02-28 23:42:47 +0000 | [diff] [blame] | 1556 | 	const char *fw_name; | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1557 |  | 
 | 1558 | 	d_fnstart(5, dev, "(i2400m %p)\n", i2400m); | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1559 |  | 
| Inaky Perez-Gonzalez | 7b43ca7 | 2009-09-14 14:10:16 -0700 | [diff] [blame] | 1560 | 	ret = -ENODEV; | 
 | 1561 | 	spin_lock(&i2400m->rx_lock); | 
 | 1562 | 	i2400m_fw = i2400m_fw_get(i2400m->fw_cached); | 
 | 1563 | 	spin_unlock(&i2400m->rx_lock); | 
 | 1564 | 	if (i2400m_fw == (void *) ~0) { | 
 | 1565 | 		dev_err(dev, "can't load firmware now!"); | 
 | 1566 | 		goto out; | 
 | 1567 | 	} else if (i2400m_fw != NULL) { | 
 | 1568 | 		dev_info(dev, "firmware %s: loading from cache\n", | 
 | 1569 | 			 i2400m->fw_name); | 
 | 1570 | 		ret = i2400m_fw_bootstrap(i2400m, i2400m_fw->fw, flags); | 
 | 1571 | 		i2400m_fw_put(i2400m_fw); | 
 | 1572 | 		goto out; | 
 | 1573 | 	} | 
 | 1574 |  | 
| Inaky Perez-Gonzalez | 1039abb | 2009-02-28 23:42:47 +0000 | [diff] [blame] | 1575 | 	/* Load firmware files to memory. */ | 
| Inaky Perez-Gonzalez | ebc5f62 | 2009-09-03 15:53:30 -0700 | [diff] [blame] | 1576 | 	for (itr = 0, bcf = NULL, ret = -ENOENT; ; itr++) { | 
| Inaky Perez-Gonzalez | 1039abb | 2009-02-28 23:42:47 +0000 | [diff] [blame] | 1577 | 		fw_name = i2400m->bus_fw_names[itr]; | 
 | 1578 | 		if (fw_name == NULL) { | 
 | 1579 | 			dev_err(dev, "Could not find a usable firmware image\n"); | 
| Inaky Perez-Gonzalez | ebc5f62 | 2009-09-03 15:53:30 -0700 | [diff] [blame] | 1580 | 			break; | 
| Inaky Perez-Gonzalez | 1039abb | 2009-02-28 23:42:47 +0000 | [diff] [blame] | 1581 | 		} | 
| Inaky Perez-Gonzalez | ebc5f62 | 2009-09-03 15:53:30 -0700 | [diff] [blame] | 1582 | 		d_printf(1, dev, "trying firmware %s (%d)\n", fw_name, itr); | 
| Inaky Perez-Gonzalez | 1039abb | 2009-02-28 23:42:47 +0000 | [diff] [blame] | 1583 | 		ret = request_firmware(&fw, fw_name, dev); | 
| Inaky Perez-Gonzalez | ebc5f62 | 2009-09-03 15:53:30 -0700 | [diff] [blame] | 1584 | 		if (ret < 0) { | 
| Inaky Perez-Gonzalez | 1039abb | 2009-02-28 23:42:47 +0000 | [diff] [blame] | 1585 | 			dev_err(dev, "fw %s: cannot load file: %d\n", | 
 | 1586 | 				fw_name, ret); | 
| Inaky Perez-Gonzalez | ebc5f62 | 2009-09-03 15:53:30 -0700 | [diff] [blame] | 1587 | 			continue; | 
 | 1588 | 		} | 
| Inaky Perez-Gonzalez | ebc5f62 | 2009-09-03 15:53:30 -0700 | [diff] [blame] | 1589 | 		i2400m->fw_name = fw_name; | 
| Inaky Perez-Gonzalez | 7b43ca7 | 2009-09-14 14:10:16 -0700 | [diff] [blame] | 1590 | 		ret = i2400m_fw_bootstrap(i2400m, fw, flags); | 
| Inaky Perez-Gonzalez | ebc5f62 | 2009-09-03 15:53:30 -0700 | [diff] [blame] | 1591 | 		release_firmware(fw); | 
| Lucas De Marchi | 25985ed | 2011-03-30 22:57:33 -0300 | [diff] [blame] | 1592 | 		if (ret >= 0)	/* firmware loaded successfully */ | 
| Inaky Perez-Gonzalez | bfc4418 | 2009-09-04 17:07:21 -0700 | [diff] [blame] | 1593 | 			break; | 
| Inaky Perez-Gonzalez | 7b43ca7 | 2009-09-14 14:10:16 -0700 | [diff] [blame] | 1594 | 		i2400m->fw_name = NULL; | 
| Inaky Perez-Gonzalez | 1039abb | 2009-02-28 23:42:47 +0000 | [diff] [blame] | 1595 | 	} | 
| Inaky Perez-Gonzalez | 7b43ca7 | 2009-09-14 14:10:16 -0700 | [diff] [blame] | 1596 | out: | 
| Inaky Perez-Gonzalez | 467cc39 | 2008-12-20 16:57:46 -0800 | [diff] [blame] | 1597 | 	d_fnend(5, dev, "(i2400m %p) = %d\n", i2400m, ret); | 
 | 1598 | 	return ret; | 
 | 1599 | } | 
 | 1600 | EXPORT_SYMBOL_GPL(i2400m_dev_bootstrap); | 
| Inaky Perez-Gonzalez | 7b43ca7 | 2009-09-14 14:10:16 -0700 | [diff] [blame] | 1601 |  | 
 | 1602 |  | 
 | 1603 | void i2400m_fw_cache(struct i2400m *i2400m) | 
 | 1604 | { | 
 | 1605 | 	int result; | 
 | 1606 | 	struct i2400m_fw *i2400m_fw; | 
 | 1607 | 	struct device *dev = i2400m_dev(i2400m); | 
 | 1608 |  | 
 | 1609 | 	/* if there is anything there, free it -- now, this'd be weird */ | 
 | 1610 | 	spin_lock(&i2400m->rx_lock); | 
 | 1611 | 	i2400m_fw = i2400m->fw_cached; | 
 | 1612 | 	spin_unlock(&i2400m->rx_lock); | 
 | 1613 | 	if (i2400m_fw != NULL && i2400m_fw != (void *) ~0) { | 
 | 1614 | 		i2400m_fw_put(i2400m_fw); | 
 | 1615 | 		WARN(1, "%s:%u: still cached fw still present?\n", | 
 | 1616 | 		     __func__, __LINE__); | 
 | 1617 | 	} | 
 | 1618 |  | 
 | 1619 | 	if (i2400m->fw_name == NULL) { | 
 | 1620 | 		dev_err(dev, "firmware n/a: can't cache\n"); | 
 | 1621 | 		i2400m_fw = (void *) ~0; | 
 | 1622 | 		goto out; | 
 | 1623 | 	} | 
 | 1624 |  | 
 | 1625 | 	i2400m_fw = kzalloc(sizeof(*i2400m_fw), GFP_ATOMIC); | 
 | 1626 | 	if (i2400m_fw == NULL) | 
 | 1627 | 		goto out; | 
 | 1628 | 	kref_init(&i2400m_fw->kref); | 
 | 1629 | 	result = request_firmware(&i2400m_fw->fw, i2400m->fw_name, dev); | 
 | 1630 | 	if (result < 0) { | 
 | 1631 | 		dev_err(dev, "firmware %s: failed to cache: %d\n", | 
 | 1632 | 			i2400m->fw_name, result); | 
 | 1633 | 		kfree(i2400m_fw); | 
 | 1634 | 		i2400m_fw = (void *) ~0; | 
 | 1635 | 	} else | 
 | 1636 | 		dev_info(dev, "firmware %s: cached\n", i2400m->fw_name); | 
 | 1637 | out: | 
 | 1638 | 	spin_lock(&i2400m->rx_lock); | 
 | 1639 | 	i2400m->fw_cached = i2400m_fw; | 
 | 1640 | 	spin_unlock(&i2400m->rx_lock); | 
 | 1641 | } | 
 | 1642 |  | 
 | 1643 |  | 
 | 1644 | void i2400m_fw_uncache(struct i2400m *i2400m) | 
 | 1645 | { | 
 | 1646 | 	struct i2400m_fw *i2400m_fw; | 
 | 1647 |  | 
 | 1648 | 	spin_lock(&i2400m->rx_lock); | 
 | 1649 | 	i2400m_fw = i2400m->fw_cached; | 
 | 1650 | 	i2400m->fw_cached = NULL; | 
 | 1651 | 	spin_unlock(&i2400m->rx_lock); | 
 | 1652 |  | 
 | 1653 | 	if (i2400m_fw != NULL && i2400m_fw != (void *) ~0) | 
 | 1654 | 		i2400m_fw_put(i2400m_fw); | 
 | 1655 | } | 
 | 1656 |  |