mirror of
https://github.com/linux-msm/qdl.git
synced 2026-10-07 06:29:17 -07:00
Without any verification on the host side, a dry-run with pre-built VIP tables (-n -D <path>) would silently succeed even when the digest tables did not match the data being flashed. The only way to catch a mismatch was to attempt a real flash and wait for the device to reject it, which wastes time and risks leaving a device in a broken state. Add hash validation to the simulator so mismatches are caught at dry-run time, before any hardware is involved. The signed initial table (DigestsToSign.bin.mbn) and all chained tables (ChainedTableOfDigests<n>.bin) are parsed as they arrive. Chain hashes linking each table to the next are verified to ensure table integrity. Per-chunk SHA256 hashes are checked in order against every XML command write and raw payload write; any mismatch is reported via ux_err() without aborting so all mismatches remain visible at the end of a run. Validation is active in exactly the intended modes: -n plain dry-run, no tables sent -- no-op -n -D <path> dry-run with VIP tables -- full hash validation -n -t <dir> digest generation -- validation disabled MAX_DIGESTS_PER_SIGNED_FILE and MAX_DIGESTS_PER_CHAINED_FILE are moved from vip.c to vip.h so sim.c can reference them. sha2.c is added to RAMDUMP_SRCS because qdl-ramdump links sim.o which now calls SHA256. Signed-off-by: Igor Opaniuk <igor.opaniuk@oss.qualcomm.com>
678 lines
20 KiB
C
678 lines
20 KiB
C
// SPDX-License-Identifier: BSD-3-Clause
|
|
/*
|
|
* Copyright (c) 2025, Qualcomm Innovation Center, Inc. All rights reserved.
|
|
*/
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
|
|
#include <libxml/parser.h>
|
|
#include <libxml/tree.h>
|
|
|
|
#include "oscompat.h"
|
|
#include "sha2.h"
|
|
#include "sim.h"
|
|
|
|
/*
|
|
* Response XML templates sent by the simulated device.
|
|
*
|
|
* The configure response echoes back MaxPayloadSizeToTargetInBytes matching
|
|
* the host's initial value (1 MiB), so firehose_try_configure() never
|
|
* triggers a second configure exchange.
|
|
*/
|
|
#define SIM_ACK \
|
|
"<?xml version=\"1.0\" encoding=\"UTF-8\" ?>" \
|
|
"<data><response value=\"ACK\" rawmode=\"false\" /></data>"
|
|
|
|
#define SIM_ACK_RAWMODE \
|
|
"<?xml version=\"1.0\" encoding=\"UTF-8\" ?>" \
|
|
"<data><response value=\"ACK\" rawmode=\"true\" /></data>"
|
|
|
|
#define SIM_CONFIGURE_ACK \
|
|
"<?xml version=\"1.0\" encoding=\"UTF-8\" ?>" \
|
|
"<data><response value=\"ACK\" MemoryName=\"ufs\"" \
|
|
" MinVersionSupported=\"1\" Version=\"1\"" \
|
|
" MaxPayloadSizeToTargetInBytes=\"1048576\"" \
|
|
" MaxPayloadSizeToTargetInBytesSupported=\"1048576\"" \
|
|
" MaxXMLSizeInBytes=\"4096\" /></data>"
|
|
|
|
/* sim_state tracks whether the sim is in XML command mode or raw binary mode */
|
|
enum sim_state {
|
|
SIM_STATE_XML, /* normal state: dequeue queued XML responses */
|
|
SIM_STATE_RAW_IN, /* receiving raw binary data from host (program) */
|
|
SIM_STATE_RAW_OUT, /* sending raw binary data to host (read) */
|
|
};
|
|
|
|
struct sim_response {
|
|
char *data;
|
|
size_t len;
|
|
struct sim_response *next;
|
|
};
|
|
|
|
/*
|
|
* VIP hash validation state.
|
|
*
|
|
* sim_vip_recv_table() extracts the data-chunk SHA256 hashes from each
|
|
* received VIP table (signed MBN or raw chained binary) and stores them in
|
|
* vip_hashes[]. sim_vip_check_chunk() computes SHA256 of each incoming chunk
|
|
* and compares it against the next stored entry.
|
|
*
|
|
* Maximum capacity:
|
|
* signed table: MAX_DIGESTS_PER_SIGNED_FILE - 1 = 53 chunk hashes
|
|
* chained tables: MAX_CHAINED_FILES x (MAX_DIGESTS_PER_CHAINED_FILE - 1)
|
|
* = 32 x 255 = 8160 chunk hashes
|
|
* total: 8213 entries x SHA256_DIGEST_LENGTH bytes = ~256 KiB
|
|
* (heap-allocated together with the enclosing struct qdl_device_sim)
|
|
*/
|
|
#define SIM_VIP_MAX_HASHES \
|
|
((MAX_DIGESTS_PER_SIGNED_FILE - 1) + \
|
|
MAX_CHAINED_FILES * (MAX_DIGESTS_PER_CHAINED_FILE - 1))
|
|
|
|
struct qdl_device_sim {
|
|
struct qdl_device base;
|
|
struct vip_table_generator *vip_gen;
|
|
bool create_digests;
|
|
|
|
/* Response queue consumed by sim_read() */
|
|
struct sim_response *resp_head;
|
|
struct sim_response *resp_tail;
|
|
|
|
enum sim_state state;
|
|
size_t raw_remaining; /* bytes of raw data left to transfer */
|
|
bool closed; /* set after power command to terminate reads fast */
|
|
|
|
/* VIP hash validation */
|
|
uint8_t vip_hashes[SIM_VIP_MAX_HASHES][SHA256_DIGEST_LENGTH];
|
|
size_t vip_hash_count; /* entries populated from received VIP tables */
|
|
size_t vip_hash_idx; /* index of the next hash to verify */
|
|
|
|
/* Chain hash linking each VIP table to the next */
|
|
uint8_t vip_chain_hash[SHA256_DIGEST_LENGTH];
|
|
bool vip_has_chain_hash;
|
|
};
|
|
|
|
static void sim_enqueue(struct qdl_device_sim *qdl_sim, const char *xml)
|
|
{
|
|
struct sim_response *resp;
|
|
|
|
resp = malloc(sizeof(*resp));
|
|
if (!resp)
|
|
err(1, "sim: failed to allocate response entry");
|
|
|
|
resp->data = strdup(xml);
|
|
if (!resp->data)
|
|
err(1, "sim: failed to duplicate response string");
|
|
|
|
resp->len = strlen(xml);
|
|
resp->next = NULL;
|
|
|
|
if (qdl_sim->resp_tail)
|
|
qdl_sim->resp_tail->next = resp;
|
|
else
|
|
qdl_sim->resp_head = resp;
|
|
|
|
qdl_sim->resp_tail = resp;
|
|
}
|
|
|
|
static void sim_enqueue_log(struct qdl_device_sim *qdl_sim, const char *handler)
|
|
{
|
|
char buf[256];
|
|
|
|
snprintf(buf, sizeof(buf),
|
|
"<?xml version=\"1.0\" encoding=\"UTF-8\" ?>"
|
|
"<data><log value=\"INFO: Calling handler for %s\" /></data>",
|
|
handler);
|
|
sim_enqueue(qdl_sim, buf);
|
|
}
|
|
|
|
static unsigned int sim_get_uint_attr(xmlNode *node, const char *name)
|
|
{
|
|
xmlChar *val;
|
|
unsigned int ret;
|
|
|
|
val = xmlGetProp(node, (xmlChar *)name);
|
|
if (!val)
|
|
return 0;
|
|
|
|
ret = (unsigned int)strtoul((char *)val, NULL, 10);
|
|
xmlFree(val);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Qualcomm MBN header structures (little-endian, packed).
|
|
*
|
|
* The signed VIP table (DigestsToSign.bin.mbn) uses one of these layouts.
|
|
* V5 and V6 share the same image_size/code_size offsets; V7 reorders the
|
|
* fields. Only V5 and V6 are supported for VIP table parsing today.
|
|
*/
|
|
struct mbn_header_v5 {
|
|
uint32_t image_id;
|
|
uint32_t header_version;
|
|
uint32_t qti_signature_size;
|
|
uint32_t qti_cert_chain_size;
|
|
uint32_t image_size;
|
|
uint32_t code_size;
|
|
uint32_t signature_ptr;
|
|
uint32_t signature_size;
|
|
uint32_t cert_chain_ptr;
|
|
uint32_t cert_chain_size;
|
|
} __attribute__((packed));
|
|
|
|
struct mbn_header_v6 {
|
|
uint32_t image_id;
|
|
uint32_t header_version;
|
|
uint32_t qti_signature_size;
|
|
uint32_t qti_cert_chain_size;
|
|
uint32_t image_size;
|
|
uint32_t code_size;
|
|
uint32_t signature_ptr;
|
|
uint32_t signature_size;
|
|
uint32_t cert_chain_ptr;
|
|
uint32_t cert_chain_size;
|
|
uint32_t qti_metadata_size;
|
|
uint32_t metadata_size;
|
|
} __attribute__((packed));
|
|
|
|
#define MBN_HEADER_VERSION_5 5
|
|
#define MBN_HEADER_VERSION_6 6
|
|
|
|
/*
|
|
* sim_vip_signed_payload() - locate the hash payload in a signed VIP MBN table
|
|
*
|
|
* DigestsToSign.bin.mbn is a Qualcomm MBN image with a packed header followed
|
|
* by the hash data, a signature, and certificate chains. The header_version
|
|
* field identifies the layout; V5 and V6 share the same image_size/code_size
|
|
* offsets. The payload begins at (file_size - image_size) and spans code_size
|
|
* bytes.
|
|
*
|
|
* Returns a pointer into @buf for the payload, or NULL if not recognised.
|
|
* Sets @out_len to the payload byte count on success.
|
|
*/
|
|
static const uint8_t *sim_vip_signed_payload(const uint8_t *buf, size_t len,
|
|
size_t *out_len)
|
|
{
|
|
struct mbn_header_v6 hdr;
|
|
size_t offset;
|
|
|
|
if (len < sizeof(struct mbn_header_v5))
|
|
return NULL;
|
|
|
|
memcpy(&hdr, buf, sizeof(struct mbn_header_v5));
|
|
|
|
if (hdr.header_version != MBN_HEADER_VERSION_5 &&
|
|
hdr.header_version != MBN_HEADER_VERSION_6)
|
|
return NULL;
|
|
|
|
if (hdr.code_size == 0 ||
|
|
hdr.code_size % SHA256_DIGEST_LENGTH != 0 ||
|
|
hdr.code_size > MAX_DIGESTS_PER_SIGNED_FILE * SHA256_DIGEST_LENGTH ||
|
|
hdr.image_size < hdr.code_size || hdr.image_size > len)
|
|
return NULL;
|
|
|
|
offset = len - hdr.image_size;
|
|
if (offset < sizeof(struct mbn_header_v5) || offset + hdr.code_size > len)
|
|
return NULL;
|
|
|
|
*out_len = hdr.code_size;
|
|
return buf + offset;
|
|
}
|
|
|
|
/*
|
|
* sim_vip_recv_table() - parse a received VIP table and store chunk hashes
|
|
*
|
|
* @is_signed: true -> DigestsToSign.bin.mbn (MBN-wrapped, signed)
|
|
* false -> ChainedTableOfDigests<n>.bin (raw binary)
|
|
*
|
|
* The last entry of a full signed table and of a non-final chained table is a
|
|
* chain hash (SHA256 of the following table) rather than a data-chunk hash.
|
|
* It is excluded from the stored list.
|
|
*
|
|
* A chained table is "final" when its raw byte count is not a multiple of
|
|
* SHA256_DIGEST_LENGTH: vip_gen_finalize() appends a single zero-padding byte
|
|
* to the final chained table so that it cannot be a USB-512-byte multiple.
|
|
*/
|
|
static void sim_vip_recv_table(struct qdl_device_sim *qdl_sim,
|
|
const void *buf, size_t len, bool is_signed)
|
|
{
|
|
const uint8_t *hashes;
|
|
size_t count, data_count;
|
|
bool has_chain_hash;
|
|
|
|
if (is_signed) {
|
|
size_t payload_len;
|
|
|
|
hashes = sim_vip_signed_payload(buf, len, &payload_len);
|
|
if (!hashes) {
|
|
ux_err("sim: VIP signed table: unrecognised format "
|
|
"(%zu bytes)\n", len);
|
|
return;
|
|
}
|
|
|
|
count = payload_len / SHA256_DIGEST_LENGTH;
|
|
/* Full table uses the last slot for the chain hash */
|
|
has_chain_hash = (count == MAX_DIGESTS_PER_SIGNED_FILE);
|
|
|
|
/* Save chain hash so the first chained table can be verified */
|
|
if (has_chain_hash) {
|
|
memcpy(qdl_sim->vip_chain_hash,
|
|
hashes + (count - 1) * SHA256_DIGEST_LENGTH,
|
|
SHA256_DIGEST_LENGTH);
|
|
qdl_sim->vip_has_chain_hash = true;
|
|
}
|
|
} else {
|
|
/* Verify this table against the chain hash from the previous table */
|
|
if (qdl_sim->vip_has_chain_hash) {
|
|
uint8_t hash[SHA256_DIGEST_LENGTH];
|
|
char got_hex[SHA256_DIGEST_STRING_LENGTH];
|
|
char exp_hex[SHA256_DIGEST_STRING_LENGTH];
|
|
SHA2_CTX ctx;
|
|
size_t i;
|
|
|
|
SHA256Init(&ctx);
|
|
SHA256Update(&ctx, buf, len);
|
|
SHA256Final(hash, &ctx);
|
|
|
|
if (memcmp(hash, qdl_sim->vip_chain_hash,
|
|
SHA256_DIGEST_LENGTH) != 0) {
|
|
for (i = 0; i < SHA256_DIGEST_LENGTH; i++) {
|
|
sprintf(got_hex + i * 2, "%02x", hash[i]);
|
|
sprintf(exp_hex + i * 2,
|
|
"%02x",
|
|
qdl_sim->vip_chain_hash[i]);
|
|
}
|
|
got_hex[SHA256_DIGEST_STRING_LENGTH - 1] = '\0';
|
|
exp_hex[SHA256_DIGEST_STRING_LENGTH - 1] = '\0';
|
|
ux_err("sim: VIP chained table chain hash mismatch\n"
|
|
" computed: %s\n"
|
|
" expected: %s\n",
|
|
got_hex, exp_hex);
|
|
} else {
|
|
ux_debug("sim: VIP chained table chain hash OK\n");
|
|
}
|
|
qdl_sim->vip_has_chain_hash = false;
|
|
}
|
|
|
|
hashes = buf;
|
|
if (len % SHA256_DIGEST_LENGTH != 0) {
|
|
/*
|
|
* Trailing zero-padding byte -> final chained table.
|
|
* All floor(len / 32) entries are chunk hashes.
|
|
*/
|
|
count = len / SHA256_DIGEST_LENGTH;
|
|
has_chain_hash = false;
|
|
} else {
|
|
/*
|
|
* Size is a multiple of 32 -> non-final chained table.
|
|
* The last entry is a chain hash to the next table.
|
|
*/
|
|
count = len / SHA256_DIGEST_LENGTH;
|
|
has_chain_hash = (count == MAX_DIGESTS_PER_CHAINED_FILE);
|
|
}
|
|
|
|
/* Save chain hash so the next chained table can be verified */
|
|
if (has_chain_hash) {
|
|
memcpy(qdl_sim->vip_chain_hash,
|
|
hashes + (count - 1) * SHA256_DIGEST_LENGTH,
|
|
SHA256_DIGEST_LENGTH);
|
|
qdl_sim->vip_has_chain_hash = true;
|
|
}
|
|
}
|
|
|
|
data_count = has_chain_hash ? count - 1 : count;
|
|
|
|
if (qdl_sim->vip_hash_count + data_count > SIM_VIP_MAX_HASHES) {
|
|
ux_err("sim: VIP table overflow (%zu + %zu > %u), "
|
|
"truncating\n",
|
|
qdl_sim->vip_hash_count, data_count, SIM_VIP_MAX_HASHES);
|
|
data_count = SIM_VIP_MAX_HASHES - qdl_sim->vip_hash_count;
|
|
}
|
|
|
|
memcpy(qdl_sim->vip_hashes[qdl_sim->vip_hash_count],
|
|
hashes, data_count * SHA256_DIGEST_LENGTH);
|
|
qdl_sim->vip_hash_count += data_count;
|
|
|
|
ux_debug("sim: loaded %zu VIP chunk hashes from %s table "
|
|
"(total: %zu)\n",
|
|
data_count, is_signed ? "signed" : "chained",
|
|
qdl_sim->vip_hash_count);
|
|
}
|
|
|
|
/*
|
|
* sim_vip_check_chunk() - SHA256 @buf/@len and verify against the next entry
|
|
*
|
|
* Called once per VIP chunk (one XML command write or one raw-data write).
|
|
* A mismatch is reported via ux_err() but does not abort, so that all
|
|
* mismatches across a full flash run are visible at once.
|
|
*/
|
|
static void sim_vip_check_chunk(struct qdl_device_sim *qdl_sim,
|
|
const void *buf, size_t len)
|
|
{
|
|
uint8_t hash[SHA256_DIGEST_LENGTH];
|
|
char got_hex[SHA256_DIGEST_STRING_LENGTH];
|
|
char exp_hex[SHA256_DIGEST_STRING_LENGTH];
|
|
const uint8_t *expected;
|
|
SHA2_CTX ctx;
|
|
size_t i;
|
|
|
|
if (qdl_sim->create_digests || !qdl_sim->vip_hash_count)
|
|
return;
|
|
|
|
SHA256Init(&ctx);
|
|
SHA256Update(&ctx, buf, len);
|
|
SHA256Final(hash, &ctx);
|
|
|
|
if (qdl_sim->vip_hash_idx >= qdl_sim->vip_hash_count) {
|
|
ux_err("sim: VIP chunk %zu has no entry in the digest table "
|
|
"(table has %zu entries)\n",
|
|
qdl_sim->vip_hash_idx, qdl_sim->vip_hash_count);
|
|
qdl_sim->vip_hash_idx++;
|
|
return;
|
|
}
|
|
|
|
expected = qdl_sim->vip_hashes[qdl_sim->vip_hash_idx];
|
|
|
|
if (memcmp(hash, expected, SHA256_DIGEST_LENGTH) == 0) {
|
|
ux_debug("sim: VIP chunk %zu hash OK\n",
|
|
qdl_sim->vip_hash_idx);
|
|
qdl_sim->vip_hash_idx++;
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < SHA256_DIGEST_LENGTH; i++) {
|
|
sprintf(got_hex + i * 2, "%02x", hash[i]);
|
|
sprintf(exp_hex + i * 2, "%02x", expected[i]);
|
|
}
|
|
got_hex[SHA256_DIGEST_STRING_LENGTH - 1] = '\0';
|
|
exp_hex[SHA256_DIGEST_STRING_LENGTH - 1] = '\0';
|
|
|
|
ux_err("sim: VIP hash mismatch for chunk %zu\n"
|
|
" computed: %s\n"
|
|
" expected: %s\n",
|
|
qdl_sim->vip_hash_idx, got_hex, exp_hex);
|
|
|
|
qdl_sim->vip_hash_idx++;
|
|
}
|
|
|
|
static int sim_open(struct qdl_device *qdl __unused,
|
|
const char *serial __unused)
|
|
{
|
|
ux_info("This is a dry-run execution of QDL. No actual flashing has been performed\n");
|
|
return 0;
|
|
}
|
|
|
|
static void sim_close(struct qdl_device *qdl __unused) {}
|
|
|
|
/*
|
|
* sim_read() - serve the next queued XML response or raw-out data
|
|
*
|
|
* Returns the number of bytes written to @buf, or a negative errno.
|
|
* Returns -ETIMEDOUT when the response queue is empty (mirroring the
|
|
* behaviour of the USB driver when no data arrives within the timeout).
|
|
* Returns -EIO after a power/reset command to let the caller drain quickly.
|
|
*/
|
|
static int sim_read(struct qdl_device *qdl, void *buf, size_t len,
|
|
unsigned int timeout __unused)
|
|
{
|
|
struct qdl_device_sim *qdl_sim = container_of(qdl, struct qdl_device_sim, base);
|
|
struct sim_response *resp;
|
|
size_t copy_len;
|
|
|
|
/*
|
|
* Always drain the response queue first. This matters for read
|
|
* commands: sim_write() sets SIM_STATE_RAW_OUT and queues the
|
|
* rawmode=true ACK at the same time, so the pending XML must be
|
|
* served before we start sending raw sector data.
|
|
*/
|
|
resp = qdl_sim->resp_head;
|
|
if (resp) {
|
|
copy_len = resp->len;
|
|
memcpy(buf, resp->data, copy_len);
|
|
qdl_sim->resp_head = resp->next;
|
|
if (!qdl_sim->resp_head)
|
|
qdl_sim->resp_tail = NULL;
|
|
free(resp->data);
|
|
free(resp);
|
|
return copy_len;
|
|
}
|
|
|
|
/*
|
|
* Queue empty and in raw-out mode: the host has received the
|
|
* rawmode=true ACK and is now reading sector data. Serve zeros
|
|
* and enqueue the final rawmode=false ACK when all sectors are done.
|
|
*/
|
|
if (qdl_sim->state == SIM_STATE_RAW_OUT) {
|
|
copy_len = MIN(len, qdl_sim->raw_remaining);
|
|
memset(buf, 0, copy_len);
|
|
qdl_sim->raw_remaining -= copy_len;
|
|
if (qdl_sim->raw_remaining == 0) {
|
|
sim_enqueue(qdl_sim, SIM_ACK);
|
|
qdl_sim->state = SIM_STATE_XML;
|
|
}
|
|
return copy_len;
|
|
}
|
|
|
|
/*
|
|
* Queue is empty. Return -EIO after power/reset so that callers
|
|
* which drain trailing log messages after the final ACK return
|
|
* immediately instead of spinning until the timeout expires.
|
|
*/
|
|
if (qdl_sim->closed)
|
|
return -EIO;
|
|
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
/*
|
|
* sim_write() - accept a host write and queue the matching device response(s)
|
|
*
|
|
* In SIM_STATE_RAW_IN the simulator counts down the expected raw payload and
|
|
* queues the final rawmode=false ACK once the last byte arrives.
|
|
*
|
|
* In SIM_STATE_XML the XML command is parsed and one or more response messages
|
|
* are pushed onto the queue for sim_read() to serve back.
|
|
*
|
|
* In dry-run mode without digest generation, binary writes that fail XML
|
|
* parsing are treated as VIP digest tables: sim_vip_recv_table() extracts the
|
|
* chunk hashes and an ACK is enqueued so that the host's post-send
|
|
* firehose_read() succeeds. Every subsequent XML command write and every
|
|
* raw-data write is then verified against the stored hashes by
|
|
* sim_vip_check_chunk().
|
|
*/
|
|
static int sim_write(struct qdl_device *qdl, const void *buf, size_t len,
|
|
unsigned int timeout __unused)
|
|
{
|
|
struct qdl_device_sim *qdl_sim = container_of(qdl, struct qdl_device_sim, base);
|
|
unsigned int num_sectors, sector_size;
|
|
xmlNode *root, *node, *child;
|
|
xmlDoc *doc;
|
|
|
|
/*
|
|
* VIP table writes (both the signed MBN and chained raw binaries) may
|
|
* arrive while SIM_STATE_RAW_IN is active, interleaved between data
|
|
* chunks of the same program operation. Intercept them here using the
|
|
* sending_table flag set exclusively by vip_transfer_send_raw(), before
|
|
* the raw-data accounting branch below.
|
|
*/
|
|
if (!qdl_sim->create_digests &&
|
|
qdl_sim->base.vip_data.sending_table) {
|
|
bool is_signed = (qdl_sim->base.vip_data.state == VIP_INIT);
|
|
|
|
sim_vip_recv_table(qdl_sim, buf, len, is_signed);
|
|
sim_enqueue(qdl_sim, SIM_ACK);
|
|
return len;
|
|
}
|
|
|
|
/* Raw binary payload for an ongoing program operation */
|
|
if (qdl_sim->state == SIM_STATE_RAW_IN) {
|
|
/*
|
|
* Each qdl_write() call maps to exactly one VIP chunk
|
|
* (firehose_do_program() loops over max_payload_size chunks,
|
|
* calling vip_gen_chunk_init/update/store once per iteration).
|
|
* Hash the entire write and verify against the digest table.
|
|
*/
|
|
sim_vip_check_chunk(qdl_sim, buf, len);
|
|
|
|
if (len >= qdl_sim->raw_remaining) {
|
|
sim_enqueue(qdl_sim, SIM_ACK);
|
|
qdl_sim->state = SIM_STATE_XML;
|
|
qdl_sim->raw_remaining = 0;
|
|
} else {
|
|
qdl_sim->raw_remaining -= len;
|
|
}
|
|
return len;
|
|
}
|
|
|
|
/* XML command: parse and enqueue appropriate response(s) */
|
|
doc = xmlReadMemory(buf, len, NULL, NULL,
|
|
XML_PARSE_NOWARNING | XML_PARSE_NOERROR);
|
|
if (!doc)
|
|
return len;
|
|
|
|
/*
|
|
* Valid XML command: verify it against the digest table before
|
|
* dispatching so that the hash index stays in step with the
|
|
* vip_gen_chunk_store() calls made during the dry-run that
|
|
* produced the table.
|
|
*/
|
|
sim_vip_check_chunk(qdl_sim, buf, len);
|
|
|
|
root = xmlDocGetRootElement(doc);
|
|
|
|
/* Locate the <data> element */
|
|
for (node = root; node; node = node->next) {
|
|
if (node->type == XML_ELEMENT_NODE &&
|
|
xmlStrcmp(node->name, (xmlChar *)"data") == 0)
|
|
break;
|
|
}
|
|
if (!node)
|
|
goto out;
|
|
|
|
/* First child element is the command */
|
|
for (child = node->children;
|
|
child && child->type != XML_ELEMENT_NODE;
|
|
child = child->next)
|
|
;
|
|
if (!child)
|
|
goto out;
|
|
|
|
if (xmlStrcmp(child->name, (xmlChar *)"configure") == 0) {
|
|
sim_enqueue_log(qdl_sim, "configure");
|
|
sim_enqueue(qdl_sim, SIM_CONFIGURE_ACK);
|
|
|
|
} else if (xmlStrcmp(child->name, (xmlChar *)"program") == 0) {
|
|
num_sectors = sim_get_uint_attr(child, "num_partition_sectors");
|
|
sector_size = sim_get_uint_attr(child, "SECTOR_SIZE_IN_BYTES");
|
|
sim_enqueue_log(qdl_sim, "program");
|
|
sim_enqueue(qdl_sim, SIM_ACK_RAWMODE);
|
|
qdl_sim->state = SIM_STATE_RAW_IN;
|
|
qdl_sim->raw_remaining = (size_t)num_sectors * sector_size;
|
|
|
|
} else if (xmlStrcmp(child->name, (xmlChar *)"read") == 0) {
|
|
num_sectors = sim_get_uint_attr(child, "num_partition_sectors");
|
|
sector_size = sim_get_uint_attr(child, "SECTOR_SIZE_IN_BYTES");
|
|
sim_enqueue_log(qdl_sim, "read");
|
|
sim_enqueue(qdl_sim, SIM_ACK_RAWMODE);
|
|
qdl_sim->state = SIM_STATE_RAW_OUT;
|
|
qdl_sim->raw_remaining = (size_t)num_sectors * sector_size;
|
|
|
|
} else if (xmlStrcmp(child->name, (xmlChar *)"erase") == 0) {
|
|
sim_enqueue_log(qdl_sim, "erase");
|
|
sim_enqueue(qdl_sim, SIM_ACK);
|
|
|
|
} else if (xmlStrcmp(child->name, (xmlChar *)"patch") == 0) {
|
|
sim_enqueue_log(qdl_sim, "patch");
|
|
sim_enqueue(qdl_sim, SIM_ACK);
|
|
|
|
} else if (xmlStrcmp(child->name, (xmlChar *)"setbootablestoragedrive") == 0) {
|
|
sim_enqueue_log(qdl_sim, "setbootablestoragedrive");
|
|
sim_enqueue(qdl_sim, SIM_ACK);
|
|
|
|
} else if (xmlStrcmp(child->name, (xmlChar *)"power") == 0) {
|
|
sim_enqueue_log(qdl_sim, "power");
|
|
sim_enqueue(qdl_sim, SIM_ACK);
|
|
/*
|
|
* Mark the sim closed so that the trailing drain read in
|
|
* firehose_reset() returns -EIO immediately rather than
|
|
* spinning until the timeout expires.
|
|
*/
|
|
qdl_sim->closed = true;
|
|
|
|
} else if (xmlStrcmp(child->name, (xmlChar *)"ufs") == 0) {
|
|
sim_enqueue_log(qdl_sim, "ufs");
|
|
sim_enqueue(qdl_sim, SIM_ACK);
|
|
|
|
} else {
|
|
/* Unknown command: respond with a generic ACK */
|
|
sim_enqueue(qdl_sim, SIM_ACK);
|
|
}
|
|
|
|
out:
|
|
xmlFreeDoc(doc);
|
|
return len;
|
|
}
|
|
|
|
static void sim_set_out_chunk_size(struct qdl_device *qdl __unused,
|
|
long size __unused)
|
|
{}
|
|
|
|
struct qdl_device *sim_init(void)
|
|
{
|
|
struct qdl_device *qdl = malloc(sizeof(struct qdl_device_sim));
|
|
|
|
if (!qdl)
|
|
return NULL;
|
|
|
|
memset(qdl, 0, sizeof(struct qdl_device_sim));
|
|
|
|
qdl->dev_type = QDL_DEVICE_SIM;
|
|
qdl->open = sim_open;
|
|
qdl->read = sim_read;
|
|
qdl->write = sim_write;
|
|
qdl->close = sim_close;
|
|
qdl->set_out_chunk_size = sim_set_out_chunk_size;
|
|
qdl->max_payload_size = 1048576;
|
|
/*
|
|
* Pre-set a non-zero sector size so that firehose_try_configure()
|
|
* skips the sector-size probe reads. Those probe commands would
|
|
* otherwise be hashed into the VIP digest table during dry-run
|
|
* digest generation (--createdigest), but they are never sent
|
|
* during the real VIP flash -- causing a hash mismatch.
|
|
*/
|
|
qdl->sector_size = 4096;
|
|
|
|
return qdl;
|
|
}
|
|
|
|
struct vip_table_generator *sim_get_vip_generator(struct qdl_device *qdl)
|
|
{
|
|
struct qdl_device_sim *qdl_sim;
|
|
|
|
if (qdl->dev_type != QDL_DEVICE_SIM)
|
|
return NULL;
|
|
|
|
qdl_sim = container_of(qdl, struct qdl_device_sim, base);
|
|
|
|
if (!qdl_sim->create_digests)
|
|
return NULL;
|
|
|
|
return qdl_sim->vip_gen;
|
|
}
|
|
|
|
bool sim_set_digest_generation(bool create_digests, struct qdl_device *qdl,
|
|
struct vip_table_generator *vip_gen)
|
|
{
|
|
struct qdl_device_sim *qdl_sim;
|
|
|
|
if (qdl->dev_type != QDL_DEVICE_SIM)
|
|
return false;
|
|
|
|
qdl_sim = container_of(qdl, struct qdl_device_sim, base);
|
|
|
|
qdl_sim->create_digests = create_digests;
|
|
qdl_sim->vip_gen = vip_gen;
|
|
|
|
return true;
|
|
}
|