/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* qmi-firmware-update -- Command line tool to update firmware in QMI devices
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* Copyright (C) 2016 Bjørn Mork
* Copyright (C) 2016 Zodiac Inflight Innovations
* Copyright (C) 2016-2017 Aleksander Morgado
*/
#include
#include
#include
#include "qfu-qdl-message.h"
#include "qfu-utils.h"
#include "qfu-enum-types.h"
/******************************************************************************/
/* QDL generic */
/* Generic message for operations that just require the command id */
typedef struct _QdlMsg QdlMsg;
struct _QdlMsg {
guint8 cmd;
} __attribute__ ((packed));
G_STATIC_ASSERT (sizeof (QdlMsg) <= QFU_QDL_MESSAGE_MAX_HEADER_SIZE);
static gsize
qdl_message_generic_build (guint8 *buffer,
gsize buffer_len,
QfuQdlCmd cmd)
{
QdlMsg *req;
g_assert (buffer_len >= sizeof (QdlMsg));
/* Create request */
req = (QdlMsg *) buffer;
req->cmd = (guint8) cmd;
g_debug ("[qfu,qdl-message] sent %s:", qfu_qdl_cmd_get_string ((QfuQdlCmd) req->cmd));
return (sizeof (QdlMsg));
}
/******************************************************************************/
/* QDL Hello */
/* feature bits */
#define QDL_FEATURE_GENERIC_UNFRAMED 0x10
#define QDL_FEATURE_QDL_UNFRAMED 0x20
#define QDL_FEATURE_BAR_MODE 0x40
typedef struct _QdlHelloReq QdlHelloReq;
struct _QdlHelloReq {
guint8 cmd; /* 0x01 */
gchar magic[32];
guint8 maxver;
guint8 minver;
guint8 features;
} __attribute__ ((packed));
G_STATIC_ASSERT (sizeof (QdlHelloReq) <= QFU_QDL_MESSAGE_MAX_HEADER_SIZE);
gsize
qfu_qdl_request_hello_build (guint8 *buffer,
gsize buffer_len,
guint8 minver,
guint8 maxver)
{
static const QdlHelloReq common_hello_req = {
.cmd = QFU_QDL_CMD_HELLO_REQ,
.magic = { "QCOM high speed protocol hst" },
.maxver = 0,
.minver = 0,
.features = QDL_FEATURE_QDL_UNFRAMED | QDL_FEATURE_GENERIC_UNFRAMED,
};
QdlHelloReq *req;
g_assert (buffer_len >= sizeof (QdlHelloReq));
/* Create request */
req = (QdlHelloReq *) buffer;
memcpy (req, &common_hello_req, sizeof (QdlHelloReq));
req->minver = minver;
req->maxver = maxver;
g_debug ("[qfu,qdl-message] sent %s:", qfu_qdl_cmd_get_string ((QfuQdlCmd) req->cmd));
g_debug ("[qfu,qdl-message] magic: %.*s", req->maxver <= 5 ? 24 : 32, req->magic);
g_debug ("[qfu,qdl-message] maximum version: %u", req->maxver);
g_debug ("[qfu,qdl-message] minimum version: %u", req->minver);
g_debug ("[qfu,qdl-message] features: 0x%02x", req->features);
return (sizeof (QdlHelloReq));
}
typedef struct _QdlHelloRsp QdlHelloRsp;
struct _QdlHelloRsp {
guint8 cmd; /* 0x02 */
gchar magic[32];
guint8 maxver;
guint8 minver;
guint32 reserved1;
guint32 reserved2;
guint8 reserved3;
guint16 reserved4;
guint16 reserved5;
guint8 features;
} __attribute__ ((packed));
G_STATIC_ASSERT (sizeof (QdlHelloRsp) <= QFU_QDL_MESSAGE_MAX_HEADER_SIZE);
gboolean
qfu_qdl_response_hello_parse (const guint8 *buffer,
gsize buffer_len,
GError **error)
{
QdlHelloRsp *rsp;
if (buffer_len != sizeof (QdlHelloRsp)) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"message size mismatch: %" G_GSIZE_FORMAT " != %" G_GSIZE_FORMAT,
buffer_len, sizeof (QdlHelloRsp));
return FALSE;
}
rsp = (QdlHelloRsp *) buffer;
g_assert (rsp->cmd == QFU_QDL_CMD_HELLO_RSP);
g_debug ("[qfu,qdl-message] received %s:", qfu_qdl_cmd_get_string ((QfuQdlCmd) rsp->cmd));
g_debug ("[qfu,qdl-message] magic: %.*s", rsp->maxver <= 5 ? 24 : 32, rsp->magic);
g_debug ("[qfu,qdl-message] maximum version: %u", rsp->maxver);
g_debug ("[qfu,qdl-message] minimum version: %u", rsp->minver);
g_debug ("[qfu,qdl-message] features: 0x%02x", rsp->features);
/* For now, ignore fields */
return TRUE;
}
/******************************************************************************/
/* QDL Error */
typedef enum {
QDL_ERROR_NONE = 0x00,
QDL_ERROR_01_RESERVED = 0x01,
QDL_ERROR_BAD_ADDR = 0x02,
QDL_ERROR_BAD_LEN = 0x03,
QDL_ERROR_BAD_PACKET = 0x04,
QDL_ERROR_BAD_CMD = 0x05,
QDL_ERROR_06 = 0x06,
QDL_ERROR_OP_FAILED = 0x07,
QDL_ERROR_BAD_FLASH_ID = 0x08,
QDL_ERROR_BAD_VOLTAGE = 0x09,
QDL_ERROR_WRITE_FAILED = 0x0a,
QDL_ERROR_11_RESERVED = 0x0b,
QDL_ERROR_BAD_SPC = 0x0c,
QDL_ERROR_POWERDOWN = 0x0d,
QDL_ERROR_UNSUPPORTED = 0x0e,
QDL_ERROR_CMD_SEQ = 0x0f,
QDL_ERROR_CLOSE = 0x10,
QDL_ERROR_BAD_FEATURES = 0x11,
QDL_ERROR_SPACE = 0x12,
QDL_ERROR_BAD_SECURITY = 0x13,
QDL_ERROR_MULTI_UNSUPPORTED = 0x14,
QDL_ERROR_POWEROFF = 0x15,
QDL_ERROR_CMD_UNSUPPORTED = 0x16,
QDL_ERROR_BAD_CRC = 0x17,
QDL_ERROR_STATE = 0x18,
QDL_ERROR_TIMEOUT = 0x19,
QDL_ERROR_IMAGE_AUTH = 0x1a,
QDL_ERROR_LAST
} QdlError;
static const gchar *qdl_error_str[] = {
[QDL_ERROR_NONE ] = "None",
[QDL_ERROR_01_RESERVED ] = "Reserved",
[QDL_ERROR_BAD_ADDR ] = "Invalid destination address",
[QDL_ERROR_BAD_LEN ] = "Invalid length",
[QDL_ERROR_BAD_PACKET ] = "Unexpected end of packet",
[QDL_ERROR_BAD_CMD ] = "Invalid command",
[QDL_ERROR_06 ] = "Reserved",
[QDL_ERROR_OP_FAILED ] = "Operation failed",
[QDL_ERROR_BAD_FLASH_ID ] = "Invalid flash intelligent ID",
[QDL_ERROR_BAD_VOLTAGE ] = "Invalid programming voltage",
[QDL_ERROR_WRITE_FAILED ] = "Write verify failed",
[QDL_ERROR_11_RESERVED ] = "Reserved",
[QDL_ERROR_BAD_SPC ] = "Invalid security code",
[QDL_ERROR_POWERDOWN ] = "Power-down failed",
[QDL_ERROR_UNSUPPORTED ] = "NAND flash programming not supported",
[QDL_ERROR_CMD_SEQ ] = "Command out of sequence",
[QDL_ERROR_CLOSE ] = "Close failed",
[QDL_ERROR_BAD_FEATURES ] = "Invalid feature bits",
[QDL_ERROR_SPACE ] = "Out of space",
[QDL_ERROR_BAD_SECURITY ] = "Invalid security mode",
[QDL_ERROR_MULTI_UNSUPPORTED ] = "Multi-image NAND not supported",
[QDL_ERROR_POWEROFF ] = "Power-off command not supported",
[QDL_ERROR_CMD_UNSUPPORTED ] = "Command not supported",
[QDL_ERROR_BAD_CRC ] = "Invalid CRC",
[QDL_ERROR_STATE ] = "Command received in invalid state",
[QDL_ERROR_TIMEOUT ] = "Receive timeout",
[QDL_ERROR_IMAGE_AUTH ] = "Image authentication error",
};
G_STATIC_ASSERT (G_N_ELEMENTS (qdl_error_str) == QDL_ERROR_LAST);
static GIOErrorEnum
qdl_error_to_gio_error_enum (QdlError err)
{
switch (err) {
case QDL_ERROR_CMD_UNSUPPORTED:
return G_IO_ERROR_NOT_SUPPORTED;
default:
return G_IO_ERROR_FAILED;
}
}
static const gchar *
qdl_error_to_string (QdlError err)
{
return (err < QDL_ERROR_LAST ? qdl_error_str[err] : "Unknown");
}
typedef struct _QdlErrRsp QdlErrRsp;
struct _QdlErrRsp {
guint8 cmd; /* 0x0d */
guint32 error;
guint8 errortxt;
} __attribute__ ((packed));
G_STATIC_ASSERT (sizeof (QdlErrRsp) <= QFU_QDL_MESSAGE_MAX_HEADER_SIZE);
gboolean
qfu_qdl_response_error_parse (const guint8 *buffer,
gsize buffer_len,
GError **error)
{
QdlErrRsp *rsp;
if (buffer_len != sizeof (QdlErrRsp)) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"message size mismatch: %" G_GSIZE_FORMAT " != %" G_GSIZE_FORMAT,
buffer_len, sizeof (QdlErrRsp));
return FALSE;
}
rsp = (QdlErrRsp *) buffer;
g_assert (rsp->cmd == QFU_QDL_CMD_ERROR);
g_debug ("[qfu,qdl-message] received %s", qfu_qdl_cmd_get_string ((QfuQdlCmd) rsp->cmd));
g_debug ("[qfu,qdl-message] error: %" G_GUINT32_FORMAT, GUINT32_FROM_LE (rsp->error));
g_debug ("[qfu,qdl-message] errortxt: %u", rsp->errortxt);
/* Always return an error in this case */
g_set_error (error, G_IO_ERROR, qdl_error_to_gio_error_enum (GUINT32_FROM_LE (rsp->error)),
"%s", qdl_error_to_string ((QdlError) GUINT32_FROM_LE (rsp->error)));
return FALSE;
}
/******************************************************************************/
/* QDL Ufopen */
typedef struct _QdlUfopenReq QdlUfopenReq;
struct _QdlUfopenReq {
guint8 cmd; /* 0x25 */
guint8 type;
guint32 length;
guint8 windowsize;
guint32 chunksize;
guint16 reserved;
} __attribute__ ((packed));
G_STATIC_ASSERT (sizeof (QdlUfopenReq) <= QFU_QDL_MESSAGE_MAX_HEADER_SIZE);
gssize
qfu_qdl_request_ufopen_build (guint8 *buffer,
gsize buffer_len,
QfuImage *image,
GCancellable *cancellable,
GError **error)
{
QdlUfopenReq *req;
g_assert (buffer_len >= sizeof (QdlUfopenReq));
/* Create request */
req = (QdlUfopenReq *) buffer;
memset (req, 0, sizeof (QdlUfopenReq));
req->cmd = QFU_QDL_CMD_OPEN_UNFRAMED_REQ;
req->type = (guint8) qfu_image_get_image_type (image);
req->windowsize = 1; /* snooped */
req->length = GUINT32_TO_LE (qfu_image_get_header_size (image) + qfu_image_get_data_size (image));
req->chunksize = GUINT32_TO_LE (qfu_image_get_data_size (image));
/* Append header */
if (qfu_image_read_header (image, buffer + sizeof (QdlUfopenReq), buffer_len - sizeof (QdlUfopenReq), cancellable, error) < 0) {
g_prefix_error (error, "couldn't read image header: ");
return -1;
}
g_debug ("[qfu,qdl-message] sent %s:", qfu_qdl_cmd_get_string ((QfuQdlCmd) req->cmd));
g_debug ("[qfu,qdl-message] type: %u", req->type);
g_debug ("[qfu,qdl-message] length: %" G_GUINT32_FORMAT, GUINT32_FROM_LE (req->length));
g_debug ("[qfu,qdl-message] window size: %u", req->windowsize);
g_debug ("[qfu,qdl-message] chunk size: %" G_GUINT32_FORMAT, GUINT32_FROM_LE (req->chunksize));
return (sizeof (QdlUfopenReq) + qfu_image_get_header_size (image));
}
typedef struct _QdlUfopenRsp QdlUfopenRsp;
struct _QdlUfopenRsp {
guint8 cmd; /* 0x26 */
guint16 status;
guint8 windowsize;
guint32 chunksize;
} __attribute__ ((packed));
G_STATIC_ASSERT (sizeof (QdlUfopenRsp) <= QFU_QDL_MESSAGE_MAX_HEADER_SIZE);
gboolean
qfu_qdl_response_ufopen_parse (const guint8 *buffer,
gsize buffer_len,
GError **error)
{
QdlUfopenRsp *rsp;
if (buffer_len != sizeof (QdlUfopenRsp)) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"message size mismatch: %" G_GSIZE_FORMAT " != %" G_GSIZE_FORMAT,
buffer_len, sizeof (QdlUfopenRsp));
return FALSE;
}
rsp = (QdlUfopenRsp *) buffer;
g_assert (rsp->cmd == QFU_QDL_CMD_OPEN_UNFRAMED_RSP);
g_debug ("[qfu,qdl-message] received %s", qfu_qdl_cmd_get_string ((QfuQdlCmd) rsp->cmd));
g_debug ("[qfu,qdl-message] status: %" G_GUINT16_FORMAT, GUINT16_FROM_LE (rsp->status));
g_debug ("[qfu,qdl-message] window size: %u", rsp->windowsize);
g_debug ("[qfu,qdl-message] chunk size: %" G_GUINT32_FORMAT, GUINT32_FROM_LE (rsp->chunksize));
/* For now, ignore all fields but build a GError based on status */
/* Return error if status != 0 */
if (rsp->status != 0) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"operation returned an error status: %" G_GUINT16_FORMAT,
GUINT16_FROM_LE (rsp->status));
return FALSE;
}
return TRUE;
}
/******************************************************************************/
/* QDL Ufwrite */
/* This request is not HDLC framed, so this "header" includes the crc */
typedef struct _QdlUfwriteReq QdlUfwriteReq;
struct _QdlUfwriteReq {
guint8 cmd; /* 0x27 */
guint16 sequence;
guint32 reserved;
guint32 chunksize;
guint16 crc;
} __attribute__ ((packed));
G_STATIC_ASSERT (sizeof (QdlUfwriteReq) <= QFU_QDL_MESSAGE_MAX_HEADER_SIZE);
gssize
qfu_qdl_request_ufwrite_build (guint8 *buffer,
gsize buffer_len,
QfuImage *image,
guint16 sequence,
GCancellable *cancellable,
GError **error)
{
QdlUfwriteReq *req;
gssize n_read;
g_assert (buffer_len >= sizeof (QdlUfwriteReq));
/* Append chunk */
n_read = qfu_image_read_data_chunk (image, sequence, buffer + sizeof (QdlUfwriteReq), buffer_len - sizeof (QdlUfwriteReq), cancellable, error);
if (n_read < 0) {
g_prefix_error (error, "couldn't read image chunk #%u: ", sequence);
return -1;
}
/* Create request after appending, so that we have correct chunksize */
req = (QdlUfwriteReq *) buffer;
memset (req, 0, sizeof (QdlUfwriteReq));
req->cmd = QFU_QDL_CMD_WRITE_UNFRAMED_REQ;
req->sequence = GUINT16_TO_LE (sequence);
req->reserved = 0;
req->chunksize = GUINT32_TO_LE ((guint32) n_read);
req->crc = GUINT16_TO_LE (qfu_utils_crc16 (buffer, sizeof (QdlUfwriteReq) - 2));
g_debug ("[qfu,qdl-message] sent %s:", qfu_qdl_cmd_get_string ((QfuQdlCmd) req->cmd));
g_debug ("[qfu,qdl-message] sequence: %" G_GUINT16_FORMAT, GUINT16_FROM_LE (req->sequence));
g_debug ("[qfu,qdl-message] chunk size: %" G_GUINT32_FORMAT, GUINT32_FROM_LE (req->chunksize));
return (sizeof (QdlUfopenReq) + n_read);
}
/* The response is HDLC framed, so the crc is part of the framing */
typedef struct _QdlUfwriteRsp QdlUfwriteRsp;
struct _QdlUfwriteRsp {
guint8 cmd; /* 0x28 */
guint16 sequence;
guint32 reserved;
guint16 status;
} __attribute__ ((packed));
G_STATIC_ASSERT (sizeof (QdlUfwriteRsp) <= QFU_QDL_MESSAGE_MAX_HEADER_SIZE);
gboolean
qfu_qdl_response_ufwrite_parse (const guint8 *buffer,
gsize buffer_len,
guint16 *sequence,
GError **error)
{
QdlUfwriteRsp *rsp;
if (buffer_len != sizeof (QdlUfwriteRsp)) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"message size mismatch: %" G_GSIZE_FORMAT " != %" G_GSIZE_FORMAT,
buffer_len, sizeof (QdlUfwriteRsp));
return FALSE;
}
rsp = (QdlUfwriteRsp *) buffer;
g_assert (rsp->cmd == QFU_QDL_CMD_WRITE_UNFRAMED_RSP);
g_debug ("[qfu,qdl-message] received %s", qfu_qdl_cmd_get_string ((QfuQdlCmd) rsp->cmd));
g_debug ("[qfu,qdl-message] status: %" G_GUINT16_FORMAT, GUINT16_FROM_LE (rsp->status));
g_debug ("[qfu,qdl-message] sequence: %" G_GUINT16_FORMAT, GUINT16_FROM_LE (rsp->sequence));
/* Only return sequence and return GError based on status */
/* Return error if status != 0 */
if (rsp->status != 0) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"operation returned an error status: %" G_GUINT16_FORMAT,
GUINT16_FROM_LE (rsp->status));
return FALSE;
}
if (sequence)
*sequence = GUINT16_FROM_LE (rsp->sequence);
return TRUE;
}
/******************************************************************************/
/* QDL Ufclose */
gsize
qfu_qdl_request_ufclose_build (guint8 *buffer,
gsize buffer_len)
{
return qdl_message_generic_build (buffer, buffer_len, QFU_QDL_CMD_CLOSE_UNFRAMED_REQ);
}
typedef struct _QdlUfcloseRsp QdlUfcloseRsp;
struct _QdlUfcloseRsp {
guint8 cmd; /* 0x2a */
guint16 status;
guint8 type;
guint8 errortxt;
} __attribute__ ((packed));
gboolean
qfu_qdl_response_ufclose_parse (const guint8 *buffer,
gsize buffer_len,
GError **error)
{
QdlUfcloseRsp *rsp;
if (buffer_len != sizeof (QdlUfcloseRsp)) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"message size mismatch: %" G_GSIZE_FORMAT " != %" G_GSIZE_FORMAT,
buffer_len, sizeof (QdlUfcloseRsp));
return FALSE;
}
rsp = (QdlUfcloseRsp *) buffer;
g_assert (rsp->cmd == QFU_QDL_CMD_CLOSE_UNFRAMED_RSP);
g_debug ("[qfu,qdl-message] received %s", qfu_qdl_cmd_get_string ((QfuQdlCmd) rsp->cmd));
g_debug ("[qfu,qdl-message] status: %" G_GUINT16_FORMAT, GUINT16_FROM_LE (rsp->status));
g_debug ("[qfu,qdl-message] type: %u", rsp->type);
g_debug ("[qfu,qdl-message] errortxt: %u", rsp->errortxt);
/* For now, ignore all fields but build a GError based on status */
/* Return error if status != 0 */
if (rsp->status != 0) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"operation returned an error status: %" G_GUINT16_FORMAT,
GUINT16_FROM_LE (rsp->status));
return FALSE;
}
return TRUE;
}
/******************************************************************************/
/* QDL session close */
gsize
qfu_qdl_request_reset_build (guint8 *buffer,
gsize buffer_len)
{
return qdl_message_generic_build (buffer, buffer_len, QFU_QDL_CMD_RESET_REQ);
}
/******************************************************************************/
/* Other unused messages */
/* 0x29 - cmd only */
/* 0x2d - cmd only */
/* 0x2e - cmd only */
typedef struct _QdlImageprefRsp QdlImageprefRsp;
struct _QdlImageprefRsp {
guint8 cmd; /* 0x2f */
guint8 entries;
struct {
guint8 type;
gchar id[16];
} image[];
} __attribute__ ((packed));