fwupd/plugins/modem-manager/fu-sahara-loader.c
2022-06-14 14:36:52 -05:00

554 lines
14 KiB
C

/*
* Copyright (C) 2021 Quectel Wireless Solutions Co., Ltd.
* Ivan Mikhanchuk <ivan.mikhanchuk@quectel.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <linux/usb/ch9.h>
#include <string.h>
#include "fu-sahara-loader.h"
#define SAHARA_VERSION 2
#define SAHARA_VERSION_COMPATIBLE 1
#define SAHARA_RAW_BUFFER_SIZE (4 * 1024)
#define IO_TIMEOUT_MS 15000
struct _FuSaharaLoader {
GObject parent_instance;
FuUsbDevice *usb_device;
int ep_in;
int ep_out;
gsize maxpktsize_in;
gsize maxpktsize_out;
};
G_DEFINE_TYPE(FuSaharaLoader, fu_sahara_loader, G_TYPE_OBJECT)
/* Protocol definitions */
typedef enum {
SAHARA_NO_CMD_ID = 0,
SAHARA_HELLO_ID,
SAHARA_HELLO_RESPONSE_ID,
SAHARA_READ_DATA_ID,
SAHARA_END_OF_IMAGE_TX_ID,
SAHARA_DONE_ID,
SAHARA_DONE_RESP_ID,
SAHARA_RESET_ID,
SAHARA_RESET_RESPONSE_ID,
SAHARA_READ_DATA_64_BIT_ID = 0x12,
SAHARA_LAST_CMD_ID
} FuSaharaCommandId;
typedef enum {
SAHARA_STATUS_SUCCESS = 0,
SAHARA_STATUS_FAILED,
SAHARA_STATUS_LAST
} FuSaharaStatusCode;
typedef enum {
SAHARA_MODE_IMAGE_TX_PENDING,
SAHARA_MODE_IMAGE_TX_COMPLETE,
SAHARA_MODE_LAST
} FuSaharaMode;
/* Sahara packet definition */
struct sahara_packet {
guint32 command_id;
guint32 length;
union {
struct {
guint32 version;
guint32 version_compatible;
guint32 max_packet_length;
guint32 mode;
} hello;
struct {
guint32 version;
guint32 version_compatible;
guint32 status;
guint32 mode;
guint32 reserved[6];
} hello_response;
struct {
guint32 image_id;
guint32 offset;
guint32 length;
} read_data;
struct {
guint32 image_id;
guint32 status;
} end_of_image_transfer;
/* done packet = header only */
struct {
guint32 image_transfer_status;
} done_response;
/* reset packet = header only */
/* reset response packet = header only */
struct {
guint64 image_id;
guint64 offset;
guint64 length;
} read_data_64bit;
};
} __attribute((packed));
/* helper functions */
static FuSaharaCommandId
sahara_packet_get_command_id(GByteArray *packet)
{
return ((struct sahara_packet *)(packet->data))->command_id;
}
static FuSaharaCommandId
sahara_packet_get_length(GByteArray *packet)
{
return ((struct sahara_packet *)(packet->data))->length;
}
/* IO functions */
static gboolean
fu_sahara_loader_find_interface(FuSaharaLoader *self, FuUsbDevice *usb_device, GError **error)
{
#if G_USB_CHECK_VERSION(0, 3, 3)
g_autoptr(GPtrArray) intfs = NULL;
GUsbDevice *g_usb_device = fu_usb_device_get_dev(usb_device);
/* all sahara devices use the same vid:pid pair */
if (g_usb_device_get_vid(g_usb_device) != 0x05c6 ||
g_usb_device_get_pid(g_usb_device) != 0x9008) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"Wrong device and/or vendor id: 0x%04x 0x%04x",
g_usb_device_get_vid(g_usb_device),
g_usb_device_get_pid(g_usb_device));
return FALSE;
}
/* Parse usb interfaces and find suitable endpoints */
intfs = g_usb_device_get_interfaces(g_usb_device, error);
if (intfs == NULL)
return FALSE;
for (guint i = 0; i < intfs->len; i++) {
GUsbInterface *intf = g_ptr_array_index(intfs, i);
if (g_usb_interface_get_class(intf) == 0xFF &&
g_usb_interface_get_subclass(intf) == 0xFF &&
g_usb_interface_get_protocol(intf) == 0xFF) {
GUsbEndpoint *ep;
g_autoptr(GPtrArray) endpoints = NULL;
endpoints = g_usb_interface_get_endpoints(intf);
if (endpoints == NULL || endpoints->len == 0)
continue;
for (guint j = 0; j < endpoints->len; j++) {
ep = g_ptr_array_index(endpoints, j);
if (g_usb_endpoint_get_direction(ep) ==
G_USB_DEVICE_DIRECTION_DEVICE_TO_HOST) {
self->ep_in = g_usb_endpoint_get_address(ep);
self->maxpktsize_in =
g_usb_endpoint_get_maximum_packet_size(ep);
} else {
self->ep_out = g_usb_endpoint_get_address(ep);
self->maxpktsize_out =
g_usb_endpoint_get_maximum_packet_size(ep);
}
}
fu_usb_device_add_interface(usb_device, g_usb_interface_get_number(intf));
return TRUE;
}
}
g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no update interface found");
return FALSE;
#else
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"this version of GUsb is not supported");
return FALSE;
#endif
}
gboolean
fu_sahara_loader_open(FuSaharaLoader *self, FuUsbDevice *usb_device, GError **error)
{
if (!fu_sahara_loader_find_interface(self, usb_device, error))
return FALSE;
if (!fu_device_open(FU_DEVICE(usb_device), error))
return FALSE;
self->usb_device = g_object_ref(usb_device);
return TRUE;
}
gboolean
fu_sahara_loader_close(FuSaharaLoader *self, GError **error)
{
if (!fu_device_close(FU_DEVICE(self->usb_device), error))
return FALSE;
g_clear_object(&self->usb_device);
return TRUE;
}
gboolean
fu_sahara_loader_qdl_is_open(FuSaharaLoader *self)
{
g_return_val_if_fail(self != NULL, FALSE);
return fu_usb_device_is_open(self->usb_device);
}
GByteArray *
fu_sahara_loader_qdl_read(FuSaharaLoader *self, GError **error)
{
gsize actual_len = 0;
g_autoptr(GByteArray) buf = g_byte_array_sized_new(SAHARA_RAW_BUFFER_SIZE);
fu_byte_array_set_size(buf, SAHARA_RAW_BUFFER_SIZE, 0x00);
if (!g_usb_device_bulk_transfer(fu_usb_device_get_dev(self->usb_device),
self->ep_in,
buf->data,
buf->len,
&actual_len,
IO_TIMEOUT_MS,
NULL,
error)) {
g_prefix_error(error, "failed to do bulk transfer (read): ");
return NULL;
}
g_byte_array_set_size(buf, actual_len);
if (g_getenv("FWUPD_MODEM_MANAGER_VERBOSE") != NULL)
g_debug("received %" G_GSIZE_FORMAT " bytes", actual_len);
return g_steal_pointer(&buf);
}
gboolean
fu_sahara_loader_qdl_write(FuSaharaLoader *self, const guint8 *data, gsize sz, GError **error)
{
gsize actual_len = 0;
g_autoptr(GPtrArray) chunks = NULL;
g_autoptr(GByteArray) bytes = NULL;
/* copy const data to mutable GByteArray */
bytes = g_byte_array_sized_new(sz);
bytes = g_byte_array_append(bytes, data, sz);
chunks = fu_chunk_array_mutable_new(bytes->data, bytes->len, 0, 0, self->maxpktsize_out);
for (guint i = 0; i < chunks->len; i++) {
FuChunk *chk = g_ptr_array_index(chunks, i);
if (!g_usb_device_bulk_transfer(fu_usb_device_get_dev(self->usb_device),
self->ep_out,
fu_chunk_get_data_out(chk),
fu_chunk_get_data_sz(chk),
&actual_len,
IO_TIMEOUT_MS,
NULL,
error)) {
g_prefix_error(error, "failed to do bulk transfer (write data): ");
return FALSE;
}
if (actual_len != fu_chunk_get_data_sz(chk)) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"only wrote %" G_GSIZE_FORMAT "bytes",
actual_len);
return FALSE;
}
}
if (sz % self->maxpktsize_out == 0) {
/* sent zlp packet if needed */
if (!g_usb_device_bulk_transfer(fu_usb_device_get_dev(self->usb_device),
self->ep_out,
NULL,
0,
NULL,
IO_TIMEOUT_MS,
NULL,
error)) {
g_prefix_error(error, "failed to do bulk transfer (write zlp): ");
return FALSE;
}
}
return TRUE;
}
gboolean
fu_sahara_loader_qdl_write_bytes(FuSaharaLoader *self, GBytes *bytes, GError **error)
{
gsize sz;
const guint8 *data = g_bytes_get_data(bytes, &sz);
return fu_sahara_loader_qdl_write(self, data, sz, error);
}
static gboolean
fu_sahara_loader_write_prog(FuSaharaLoader *self,
guint32 offset,
guint32 length,
GBytes *prog,
GError **error)
{
gsize sz;
const guint8 *data = g_bytes_get_data(prog, &sz);
g_return_val_if_fail(offset + length <= sz, FALSE);
if (g_getenv("FWUPD_MODEM_MANAGER_VERBOSE") != NULL) {
g_debug("SENDING --> RAW_DATA: %u bytes (offset = %u, total = %" G_GSIZE_FORMAT ")",
length,
offset,
sz);
}
return fu_sahara_loader_qdl_write(self, &data[offset], length, error);
}
static gboolean
fu_sahara_loader_send_packet(FuSaharaLoader *self, GByteArray *pkt, GError **error)
{
guint8 *data = pkt->data;
gsize sz = pkt->len;
g_return_val_if_fail(pkt != NULL, FALSE);
if (g_getenv("FWUPD_MODEM_MANAGER_VERBOSE") != NULL)
fu_dump_raw(G_LOG_DOMAIN, "tx packet", pkt->data, pkt->len);
return fu_sahara_loader_qdl_write(self, data, sz, error);
}
/* packet composers */
static GByteArray *
fu_sahara_create_byte_array_from_packet(const struct sahara_packet *pkt)
{
GByteArray *self;
g_autoptr(GError) error_local = NULL;
g_return_val_if_fail(pkt != NULL, NULL);
self = g_byte_array_sized_new(pkt->length);
fu_byte_array_set_size(self, pkt->length, 0x00);
if (!fu_memcpy_safe(self->data,
self->len,
0,
(gconstpointer)pkt,
sizeof(struct sahara_packet),
0,
pkt->length,
&error_local)) {
g_debug("sahara create packet failed: %s", error_local->message);
return NULL;
}
return self;
}
static GByteArray *
fu_sahara_loader_compose_reset_packet(void)
{
guint32 len = 0x08;
struct sahara_packet pkt = {.command_id = GUINT32_TO_LE(SAHARA_RESET_ID),
.length = GUINT32_TO_LE(len),
{{0}}};
return fu_sahara_create_byte_array_from_packet(&pkt);
}
static GByteArray *
fu_sahara_loader_compose_hello_response_packet(FuSaharaMode mode)
{
guint32 len = 0x30;
struct sahara_packet pkt = {.command_id = GUINT32_TO_LE(SAHARA_HELLO_RESPONSE_ID),
.length = GUINT32_TO_LE(len),
{{0}}};
pkt.hello_response.version = GUINT32_TO_LE(SAHARA_VERSION);
pkt.hello_response.version_compatible = GUINT32_TO_LE(SAHARA_VERSION_COMPATIBLE);
pkt.hello_response.status = GUINT32_TO_LE(SAHARA_STATUS_SUCCESS);
pkt.hello_response.mode = GUINT32_TO_LE(SAHARA_MODE_IMAGE_TX_PENDING);
return fu_sahara_create_byte_array_from_packet(&pkt);
}
static GByteArray *
fu_sahara_loader_compose_done_packet(void)
{
guint32 len = 0x08;
struct sahara_packet pkt = {.command_id = GUINT32_TO_LE(SAHARA_DONE_ID),
.length = GUINT32_TO_LE(len),
{{0}}};
return fu_sahara_create_byte_array_from_packet(&pkt);
}
static gboolean
fu_sahara_loader_send_reset_packet(FuSaharaLoader *self, GError **error)
{
g_autoptr(GByteArray) rx_packet = NULL;
g_autoptr(GByteArray) tx_packet = NULL;
tx_packet = fu_sahara_loader_compose_reset_packet();
if (!fu_sahara_loader_send_packet(self, tx_packet, error)) {
g_prefix_error(error, "Failed to send reset packet: ");
return FALSE;
}
rx_packet = fu_sahara_loader_qdl_read(self, error);
if (rx_packet == NULL ||
sahara_packet_get_command_id(rx_packet) != SAHARA_RESET_RESPONSE_ID) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"Failed to receive RESET_RESPONSE packet");
return FALSE;
}
if (g_getenv("FWUPD_MODEM_MANAGER_VERBOSE") != NULL)
g_debug("reset succeeded");
return TRUE;
}
static gboolean
fu_sahara_loader_wait_hello_rsp(FuSaharaLoader *self, GError **error)
{
g_autoptr(GByteArray) rx_packet = NULL;
g_autoptr(GByteArray) tx_packet = NULL;
rx_packet = fu_sahara_loader_qdl_read(self, error);
if (rx_packet == NULL) {
g_autoptr(GByteArray) ping = NULL;
ping = g_byte_array_sized_new(1);
g_byte_array_set_size(ping, 1);
fu_sahara_loader_send_packet(self, ping, NULL);
rx_packet = fu_sahara_loader_qdl_read(self, error);
}
g_return_val_if_fail(rx_packet != NULL, FALSE);
if (g_getenv("FWUPD_MODEM_MANAGER_VERBOSE") != NULL)
fu_dump_raw(G_LOG_DOMAIN, "rx packet", rx_packet->data, rx_packet->len);
if (sahara_packet_get_command_id(rx_packet) != SAHARA_HELLO_ID) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"Received a different packet while waiting for the HELLO packet");
fu_sahara_loader_send_reset_packet(self, NULL);
return FALSE;
}
tx_packet = fu_sahara_loader_compose_hello_response_packet(SAHARA_MODE_IMAGE_TX_PENDING);
return fu_sahara_loader_send_packet(self, tx_packet, error);
}
/* main routine */
gboolean
fu_sahara_loader_run(FuSaharaLoader *self, GBytes *prog, GError **error)
{
g_return_val_if_fail(prog != NULL, FALSE);
if (g_getenv("FWUPD_MODEM_MANAGER_VERBOSE") != NULL)
g_debug("STATE -- SAHARA_WAIT_HELLO");
if (!fu_sahara_loader_wait_hello_rsp(self, error))
return FALSE;
while (TRUE) {
guint32 command_id;
struct sahara_packet *pkt;
g_autoptr(GByteArray) rx_packet = NULL;
g_autoptr(GByteArray) tx_packet = NULL;
g_autoptr(GError) error_local = NULL;
if (g_getenv("FWUPD_MODEM_MANAGER_VERBOSE") != NULL)
g_debug("STATE -- SAHARA_WAIT_COMMAND");
rx_packet = fu_sahara_loader_qdl_read(self, error);
if (rx_packet == NULL)
break;
if (rx_packet->len != sahara_packet_get_length(rx_packet)) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"Received packet length is not matching");
break;
}
if (g_getenv("FWUPD_MODEM_MANAGER_VERBOSE") != NULL) {
fu_dump_raw(G_LOG_DOMAIN, "rx_packet", rx_packet->data, rx_packet->len);
}
command_id = sahara_packet_get_command_id(rx_packet);
pkt = (struct sahara_packet *)(rx_packet->data);
if (command_id == SAHARA_HELLO_ID) {
tx_packet = fu_sahara_loader_compose_hello_response_packet(
SAHARA_MODE_IMAGE_TX_PENDING);
fu_sahara_loader_send_packet(self, tx_packet, &error_local);
} else if (command_id == SAHARA_READ_DATA_ID) {
guint32 offset = pkt->read_data.offset;
guint32 length = pkt->read_data.length;
fu_sahara_loader_write_prog(self, offset, length, prog, &error_local);
} else if (command_id == SAHARA_READ_DATA_64_BIT_ID) {
guint64 offset = pkt->read_data_64bit.offset;
guint64 length = pkt->read_data_64bit.length;
fu_sahara_loader_write_prog(self, offset, length, prog, &error_local);
} else if (command_id == SAHARA_END_OF_IMAGE_TX_ID) {
guint32 status = pkt->end_of_image_transfer.status;
if (status == SAHARA_STATUS_SUCCESS) {
tx_packet = fu_sahara_loader_compose_done_packet();
fu_sahara_loader_send_packet(self, tx_packet, &error_local);
}
} else if (command_id == SAHARA_DONE_RESP_ID) {
return TRUE;
} else {
g_warning("Unexpected packet received: cmd_id = %u, len = %u",
command_id,
sahara_packet_get_length(rx_packet));
}
if (error_local != NULL)
g_warning("%s", error_local->message);
}
fu_sahara_loader_send_reset_packet(self, NULL);
return FALSE;
}
static void
fu_sahara_loader_init(FuSaharaLoader *self)
{
}
static void
fu_sahara_loader_finalize(GObject *object)
{
G_OBJECT_CLASS(fu_sahara_loader_parent_class)->finalize(object);
}
static void
fu_sahara_loader_class_init(FuSaharaLoaderClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS(klass);
object_class->finalize = fu_sahara_loader_finalize;
}
FuSaharaLoader *
fu_sahara_loader_new(void)
{
return g_object_new(FU_TYPE_SAHARA_LOADER, NULL);
}