/** * i2400m_bm_cmd - Execute a boot mode command * * @cmd: buffer containing the command data (pointing at the header). * This data can be ANYWHERE (for USB, we will copy it to an * specific buffer). Make sure everything is in proper little * endian. * * A raw buffer can be also sent, just cast it and set flags to * I2400M_BM_CMD_RAW. * * This function will generate a checksum for you if the * checksum bit in the command is set (unless I2400M_BM_CMD_RAW * is set). * * You can use the i2400m->bm_cmd_buf to stage your commands and * send them. * * If NULL, no command is sent (we just wait for an ack). * * @cmd_size: size of the command. Will be auto padded to the * bus-specific drivers padding requirements. * * @ack: buffer where to place the acknowledgement. If it is a regular * command response, all fields will be returned with the right, * native endianess. * * You *cannot* use i2400m->bm_ack_buf for this buffer. * * @ack_size: size of @ack, 16 aligned; you need to provide at least * sizeof(*ack) bytes and then enough to contain the return data * from the command * * @flags: see I2400M_BM_CMD_* above. * * @returns: bytes received by the notification; if < 0, an errno code * denoting an error or: * * -ERESTARTSYS The device has rebooted * * Executes a boot-mode command and waits for a response, doing basic * validation on it; if a zero length response is received, it retries * waiting for a response until a non-zero one is received (timing out * after %I2400M_BOOT_RETRIES retries). */ static ssize_t i2400m_bm_cmd(struct i2400m *i2400m, const struct i2400m_bootrom_header *cmd, size_t cmd_size, struct i2400m_bootrom_header *ack, size_t ack_size, int flags) { ssize_t result = -ENOMEM, rx_bytes; struct device *dev = i2400m_dev(i2400m); int opcode = cmd == NULL ? -1 : i2400m_brh_get_opcode(cmd); d_fnstart(6, dev, "(i2400m %p cmd %p size %zu ack %p size %zu)\n", i2400m, cmd, cmd_size, ack, ack_size); BUG_ON(ack_size < sizeof(*ack)); BUG_ON(i2400m->boot_mode == 0); if (cmd != NULL) { /* send the command */ result = i2400m->bus_bm_cmd_send(i2400m, cmd, cmd_size, flags); if (result < 0) goto error_cmd_send; if ((flags & I2400M_BM_CMD_RAW) == 0) d_printf(5, dev, "boot-mode cmd %d csum %u rr %u da %u: " "addr 0x%04x size %u block csum 0x%04x\n", opcode, i2400m_brh_get_use_checksum(cmd), i2400m_brh_get_response_required(cmd), i2400m_brh_get_direct_access(cmd), cmd->target_addr, cmd->data_size, cmd->block_checksum); } result = i2400m->bus_bm_wait_for_ack(i2400m, ack, ack_size); if (result < 0) { dev_err(dev, "boot-mode cmd %d: error waiting for an ack: %d\n", opcode, (int) result); /* bah, %zd doesn't work */ goto error_wait_for_ack; } rx_bytes = result; /* verify the ack and read more if necessary [result is the * final amount of bytes we get in the ack] */ result = __i2400m_bm_ack_verify(i2400m, opcode, ack, ack_size, flags); if (result < 0) goto error_bad_ack; /* Don't you love this stack of empty targets? Well, I don't * either, but it helps track exactly who comes in here and * why :) */ result = rx_bytes; error_bad_ack: error_wait_for_ack: error_cmd_send: d_fnend(6, dev, "(i2400m %p cmd %p size %zu ack %p size %zu) = %d\n", i2400m, cmd, cmd_size, ack, ack_size, (int) result); return result; }
ssize_t i2400ms_bus_bm_cmd_send(struct i2400m *i2400m, const struct i2400m_bootrom_header *_cmd, size_t cmd_size, int flags) { ssize_t result; struct device *dev = i2400m_dev(i2400m); struct i2400ms *i2400ms = container_of(i2400m, struct i2400ms, i2400m); int opcode = _cmd == NULL ? -1 : i2400m_brh_get_opcode(_cmd); struct i2400m_bootrom_header *cmd; size_t cmd_size_a = ALIGN(cmd_size, I2400MS_BLK_SIZE); d_fnstart(5, dev, "(i2400m %p cmd %p size %zu)\n", i2400m, _cmd, cmd_size); result = -E2BIG; if (cmd_size > I2400M_BM_CMD_BUF_SIZE) goto error_too_big; if (_cmd != i2400m->bm_cmd_buf) memmove(i2400m->bm_cmd_buf, _cmd, cmd_size); cmd = i2400m->bm_cmd_buf; if (cmd_size_a > cmd_size) memset(i2400m->bm_cmd_buf + cmd_size, 0, cmd_size_a - cmd_size); if ((flags & I2400M_BM_CMD_RAW) == 0) { if (WARN_ON(i2400m_brh_get_response_required(cmd) == 0)) dev_warn(dev, "SW BUG: response_required == 0\n"); i2400m_bm_cmd_prepare(cmd); } d_printf(4, dev, "BM cmd %d: %zu bytes (%zu padded)\n", opcode, cmd_size, cmd_size_a); d_dump(5, dev, cmd, cmd_size); sdio_claim_host(i2400ms->func); result = sdio_memcpy_toio(i2400ms->func, I2400MS_DATA_ADDR, i2400m->bm_cmd_buf, cmd_size_a); sdio_release_host(i2400ms->func); if (result < 0) { dev_err(dev, "BM cmd %d: cannot send: %ld\n", opcode, (long) result); goto error_cmd_send; } result = cmd_size; error_cmd_send: error_too_big: d_fnend(5, dev, "(i2400m %p cmd %p size %zu) = %d\n", i2400m, _cmd, cmd_size, (int) result); return result; }
/* * Send a boot-mode command over the bulk-out pipe * * Command can be a raw command, which requires no preparation (and * which might not even be following the command format). Checks that * the right amount of data was transferred. * * To satisfy USB requirements (no onstack, vmalloc or in data segment * buffers), we copy the command to i2400m->bm_cmd_buf and send it from * there. * * @flags: pass thru from i2400m_bm_cmd() * @return: cmd_size if ok, < 0 errno code on error. */ ssize_t i2400mu_bus_bm_cmd_send(struct i2400m *i2400m, const struct i2400m_bootrom_header *_cmd, size_t cmd_size, int flags) { ssize_t result; struct device *dev = i2400m_dev(i2400m); struct i2400mu *i2400mu = container_of(i2400m, struct i2400mu, i2400m); int opcode = _cmd == NULL ? -1 : i2400m_brh_get_opcode(_cmd); struct i2400m_bootrom_header *cmd; size_t cmd_size_a = ALIGN(cmd_size, 16); /* USB restriction */ d_fnstart(8, dev, "(i2400m %p cmd %p size %zu)\n", i2400m, _cmd, cmd_size); result = -E2BIG; if (cmd_size > I2400M_BM_CMD_BUF_SIZE) goto error_too_big; if (_cmd != i2400m->bm_cmd_buf) memmove(i2400m->bm_cmd_buf, _cmd, cmd_size); cmd = i2400m->bm_cmd_buf; if (cmd_size_a > cmd_size) /* Zero pad space */ memset(i2400m->bm_cmd_buf + cmd_size, 0, cmd_size_a - cmd_size); if ((flags & I2400M_BM_CMD_RAW) == 0) { if (WARN_ON(i2400m_brh_get_response_required(cmd) == 0)) dev_warn(dev, "SW BUG: response_required == 0\n"); i2400m_bm_cmd_prepare(cmd); } result = i2400mu_tx_bulk_out(i2400mu, i2400m->bm_cmd_buf, cmd_size); if (result < 0) { dev_err(dev, "boot-mode cmd %d: cannot send: %zd\n", opcode, result); goto error_cmd_send; } if (result != cmd_size) { /* all was transferred? */ dev_err(dev, "boot-mode cmd %d: incomplete transfer " "(%zu vs %zu submitted)\n", opcode, result, cmd_size); result = -EIO; goto error_cmd_size; } error_cmd_size: error_cmd_send: error_too_big: d_fnend(8, dev, "(i2400m %p cmd %p size %zu) = %zd\n", i2400m, _cmd, cmd_size, result); return result; }
/* * Download a BCF file's sections to the device * * @i2400m: device descriptor * @bcf: pointer to firmware data (first header followed by the * payloads). Assumed verified and consistent. * @bcf_len: length (in bytes) of the @bcf buffer. * * Returns: < 0 errno code on error or the offset to the jump instruction. * * Given a BCF file, downloads each section (a command and a payload) * to the device's address space. Actually, it just executes each * command i the BCF file. * * The section size has to be aligned to 4 bytes AND the padding has * to be taken from the firmware file, as the signature takes it into * account. */ static ssize_t i2400m_dnload_bcf(struct i2400m *i2400m, const struct i2400m_bcf_hdr *bcf, size_t bcf_len) { ssize_t ret; struct device *dev = i2400m_dev(i2400m); size_t offset, /* iterator offset */ data_size, /* Size of the data payload */ section_size, /* Size of the whole section (cmd + payload) */ section = 1; const struct i2400m_bootrom_header *bh; struct i2400m_bootrom_header ack; d_fnstart(3, dev, "(i2400m %p bcf %p bcf_len %zu)\n", i2400m, bcf, bcf_len); /* Iterate over the command blocks in the BCF file that start * after the header */ offset = le32_to_cpu(bcf->header_len) * sizeof(u32); while (1) { /* start sending the file */ bh = (void *) bcf + offset; data_size = le32_to_cpu(bh->data_size); section_size = ALIGN(sizeof(*bh) + data_size, 4); d_printf(7, dev, "downloading section #%zu (@%zu %zu B) to 0x%08x\n", section, offset, sizeof(*bh) + data_size, le32_to_cpu(bh->target_addr)); /* * We look for JUMP cmd from the bootmode header, * either I2400M_BRH_SIGNED_JUMP for secure boot * or I2400M_BRH_JUMP for unsecure boot, the last chunk * should be the bootmode header with JUMP cmd. */ if (i2400m_brh_get_opcode(bh) == I2400M_BRH_SIGNED_JUMP || i2400m_brh_get_opcode(bh) == I2400M_BRH_JUMP) { d_printf(5, dev, "jump found @%zu\n", offset); break; } if (offset + section_size > bcf_len) { dev_err(dev, "fw %s: bad section #%zu, " "end (@%zu) beyond EOF (@%zu)\n", i2400m->fw_name, section, offset + section_size, bcf_len); ret = -EINVAL; goto error_section_beyond_eof; } __i2400m_msleep(20); ret = i2400m_bm_cmd(i2400m, bh, section_size, &ack, sizeof(ack), I2400M_BM_CMD_RAW); if (ret < 0) { dev_err(dev, "fw %s: section #%zu (@%zu %zu B) " "failed %d\n", i2400m->fw_name, section, offset, sizeof(*bh) + data_size, (int) ret); goto error_send; } offset += section_size; section++; } ret = offset; error_section_beyond_eof: error_send: d_fnend(3, dev, "(i2400m %p bcf %p bcf_len %zu) = %d\n", i2400m, bcf, bcf_len, (int) ret); return ret; }
/* * Verify the ack data received * * Given a reply to a boot mode command, chew it and verify everything * is ok. * * @opcode: opcode which generated this ack. For error messages. * @ack: pointer to ack data we received * @ack_size: size of that data buffer * @flags: I2400M_BM_CMD_* flags we called the command with. * * Way too long function -- maybe it should be further split */ static ssize_t __i2400m_bm_ack_verify(struct i2400m *i2400m, int opcode, struct i2400m_bootrom_header *ack, size_t ack_size, int flags) { ssize_t result = -ENOMEM; struct device *dev = i2400m_dev(i2400m); d_fnstart(8, dev, "(i2400m %p opcode %d ack %p size %zu)\n", i2400m, opcode, ack, ack_size); if (ack_size < sizeof(*ack)) { result = -EIO; dev_err(dev, "boot-mode cmd %d: HW BUG? notification didn't " "return enough data (%zu bytes vs %zu expected)\n", opcode, ack_size, sizeof(*ack)); goto error_ack_short; } result = i2400m_is_boot_barker(i2400m, ack, ack_size); if (result >= 0) { result = -ERESTARTSYS; d_printf(6, dev, "boot-mode cmd %d: HW boot barker\n", opcode); goto error_reboot; } if (ack_size == sizeof(i2400m_ACK_BARKER) && memcmp(ack, i2400m_ACK_BARKER, sizeof(*ack)) == 0) { result = -EISCONN; d_printf(3, dev, "boot-mode cmd %d: HW reboot ack barker\n", opcode); goto error_reboot_ack; } result = 0; if (flags & I2400M_BM_CMD_RAW) goto out_raw; ack->data_size = le32_to_cpu(ack->data_size); ack->target_addr = le32_to_cpu(ack->target_addr); ack->block_checksum = le32_to_cpu(ack->block_checksum); d_printf(5, dev, "boot-mode cmd %d: notification for opcode %u " "response %u csum %u rr %u da %u\n", opcode, i2400m_brh_get_opcode(ack), i2400m_brh_get_response(ack), i2400m_brh_get_use_checksum(ack), i2400m_brh_get_response_required(ack), i2400m_brh_get_direct_access(ack)); result = -EIO; if (i2400m_brh_get_signature(ack) != 0xcbbc) { dev_err(dev, "boot-mode cmd %d: HW BUG? wrong signature " "0x%04x\n", opcode, i2400m_brh_get_signature(ack)); goto error_ack_signature; } if (opcode != -1 && opcode != i2400m_brh_get_opcode(ack)) { dev_err(dev, "boot-mode cmd %d: HW BUG? " "received response for opcode %u, expected %u\n", opcode, i2400m_brh_get_opcode(ack), opcode); goto error_ack_opcode; } if (i2400m_brh_get_response(ack) != 0) { /* failed? */ dev_err(dev, "boot-mode cmd %d: error; hw response %u\n", opcode, i2400m_brh_get_response(ack)); goto error_ack_failed; } if (ack_size < ack->data_size + sizeof(*ack)) { dev_err(dev, "boot-mode cmd %d: SW BUG " "driver provided only %zu bytes for %zu bytes " "of data\n", opcode, ack_size, (size_t) le32_to_cpu(ack->data_size) + sizeof(*ack)); goto error_ack_short_buffer; } result = ack_size; /* Don't you love this stack of empty targets? Well, I don't * either, but it helps track exactly who comes in here and * why :) */ error_ack_short_buffer: error_ack_failed: error_ack_opcode: error_ack_signature: out_raw: error_reboot_ack: error_reboot: error_ack_short: d_fnend(8, dev, "(i2400m %p opcode %d ack %p size %zu) = %d\n", i2400m, opcode, ack, ack_size, (int) result); return result; }