/* -*- 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));