Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions examples/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ cc_binary(
"htool_console.h",
"htool_constants.h",
"htool_dbus.c",
"htool_dfu.c",
"htool_dfu.h",
"htool_i2c.c",
"htool_i2c.h",
"htool_jtag.c",
Expand Down Expand Up @@ -135,6 +137,7 @@ cc_binary(
"//protocol:authz_record",
"//protocol:chipinfo",
"//protocol:controlled_storage",
"//protocol:dfu_hostcmd",
"//protocol:hello",
"//protocol:host_cmd",
"//protocol:i2c",
Expand Down
13 changes: 13 additions & 0 deletions examples/htool.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include "htool_authz_command.h"
#include "htool_cmd.h"
#include "htool_console.h"
#include "htool_dfu.h"
#include "htool_i2c.h"
#include "htool_jtag.h"
#include "htool_key_rotation.h"
Expand Down Expand Up @@ -929,6 +930,18 @@ static const struct htool_cmd CMDS[] = {
{HTOOL_POSITIONAL, .name = "source-file"}, {}},
.func = htool_payload_info,
},
{
.verbs = (const char*[]){"dfu", "update", NULL},
.desc = "Directly install a PIE-RoT fwupdate.",
.params =
(const struct htool_param[]){
{HTOOL_POSITIONAL, .name = "fwupdate-file",
.desc = "A .fwupdate file compatible with this device."},
{HTOOL_FLAG_VALUE, .name = "reset",
.desc = "warm, cold, or none", .default_value = "warm"},
{}},
.func = htool_dfu_update,
},
{.verbs = (const char*[]){"flash_spi_info", NULL},
.desc = "Get SPI NOR flash info.",
.params = (const struct htool_param[]){{}},
Expand Down
91 changes: 91 additions & 0 deletions examples/htool_dfu.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@

#include "htool_dfu.h"

#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include "htool.h"
#include "htool_cmd.h"
#include "protocol/dfu_hostcmd.h"

int htool_dfu_update(const struct htool_invocation* inv) {
struct libhoth_device* dev = htool_libhoth_device();
if (!dev) {
return -1;
}

uint32_t complete_flags = 0;
const char* reset_arg;
if (htool_get_param_string(inv, "reset", &reset_arg)) {
return -1;
}
if (strcmp(reset_arg, "warm") == 0) {
complete_flags |= HOTH_DFU_COMPLETE_FLAGS_WARM_RESTART;
} else if (strcmp(reset_arg, "cold") == 0) {
complete_flags |= HOTH_DFU_COMPLETE_FLAGS_COLD_RESTART;
} else if (strcmp(reset_arg, "none") == 0) {
// No flags needed
} else {
fprintf(
stderr,
"Invalid value for --reset: %s. Must be 'warm', 'cold', or 'none'.\n",
reset_arg);
return -1;
}

const char* fwupdate_file;
if (htool_get_param_string(inv, "fwupdate-file", &fwupdate_file)) {
return -1;
}

int fd = open(fwupdate_file, O_RDONLY, 0);
if (fd == -1) {
fprintf(stderr, "Error opening file %s: %s\n", fwupdate_file,
strerror(errno));
return -1;
}

int retval = -1;

struct stat statbuf;
if (fstat(fd, &statbuf)) {
fprintf(stderr, "fstat error: %s\n", strerror(errno));
goto cleanup;
}
if (statbuf.st_size > SIZE_MAX) {
fprintf(stderr, "file too large\n");
goto cleanup;
}

uint8_t* image = mmap(NULL, statbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (image == MAP_FAILED) {
fprintf(stderr, "mmap error: %s\n", strerror(errno));
goto cleanup;
}

if (libhoth_dfu_update(dev, image, statbuf.st_size, complete_flags) != 0) {
fprintf(stderr, "DFU update failed.\n");
goto cleanup2;
}
retval = 0;

cleanup2:
int ret = munmap(image, statbuf.st_size);
if (ret != 0) {
fprintf(stderr, "munmap error: %d\n", ret);
}

cleanup:
ret = close(fd);
if (ret != 0) {
fprintf(stderr, "close error: %d\n", ret);
}
return retval;
}
30 changes: 30 additions & 0 deletions examples/htool_dfu.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef LIBHOTH_EXAMPLES_HTOOL_DFU_H_
#define LIBHOTH_EXAMPLES_HTOOL_DFU_H_

#ifdef __cplusplus
extern "C" {
#endif

struct htool_invocation;

int htool_dfu_update(const struct htool_invocation* inv);

#ifdef __cplusplus
}
#endif

#endif // LIBHOTH_EXAMPLES_HTOOL_DFU_H_
1 change: 1 addition & 0 deletions examples/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ executable(
'htool_cmd.c',
'htool_console.c',
'htool_dbus.c',
'htool_dfu.c',
'htool_i2c.c',
'htool_jtag.c',
'htool_mtd.c',
Expand Down
10 changes: 10 additions & 0 deletions protocol/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -414,3 +414,13 @@ cc_test(
"@googletest//:gtest_main",
],
)

cc_library(
name = "dfu_hostcmd",
srcs = ["dfu_hostcmd.c"],
hdrs = ["dfu_hostcmd.h"],
deps = [
":host_cmd",
"//transports:libhoth_device",
],
)
88 changes: 88 additions & 0 deletions protocol/dfu_hostcmd.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@

#include "dfu_hostcmd.h"

#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// for MIN()
#include <sys/param.h>
#include <sys/random.h>
#include <time.h>
#include <unistd.h>

#include "protocol/host_cmd.h"

static int generate_random_nonce(struct hoth_dfu_session_id* session_id) {
ssize_t ret = getrandom(&session_id->nonce, sizeof(session_id->nonce), 0);
if (ret == -1) {
perror("getrandom");
return -1;
}
return 0;
}

int libhoth_dfu_update(struct libhoth_device* dev, const uint8_t* image,
size_t image_size, uint32_t complete_flags) {
struct hoth_dfu_session_id session_id = {
.target = HOTH_DFU_TARGET_EARLGREY_FW_UPDATE,
};
if (generate_random_nonce(&session_id) != 0) {
fprintf(stderr, "Failed to generate random nonce.\n");
return -1;
}

struct {
struct hoth_dfu_write_request_header hdr;
uint8_t data[1000];
} request = {.hdr = {.session_id = session_id}};
static_assert(sizeof(request) == LIBHOTH_MAILBOX_SIZE - 8);

size_t bytes_sent = 0;
while (bytes_sent < image_size) {
request.hdr.flags = bytes_sent == 0 ? HOTH_DFU_WRITE_FLAGS_NEW_SESSION : 0;

size_t chunk_len = MIN(sizeof(request.data), image_size - bytes_sent);
memcpy(request.data, &image[bytes_sent], chunk_len);

size_t response_len;
int ret = libhoth_hostcmd_exec(dev, HOTH_CMD_DFU_WRITE, 0, &request,
sizeof(request.hdr) + chunk_len, NULL, 0,
&response_len);
if (ret != 0) {
fprintf(stderr, "DFU write failed with error code: %d\n", ret);
return -1;
}
if (response_len != 0) {
fprintf(stderr, "DFU write expected 0 response bytes, got %zu\n",
response_len);
return -1;
}
bytes_sent += chunk_len;
}

fprintf(stderr,
"Completed sending fwupdate via DFU WRITE; sending DFU_COMPLETE to "
"restart\n");

struct hoth_dfu_complete_request complete_request = {
.session_id = session_id,
.flags = complete_flags,
};
size_t response_len = 0;
int ret =
libhoth_hostcmd_exec(dev, HOTH_CMD_DFU_COMPLETE, 0, &complete_request,
sizeof(complete_request), NULL, 0, &response_len);
if (ret != 0) {
fprintf(stderr,
"DFU complete failed with error code: %d; ignoring as the "
"chip may have already restarted.\n",
ret);
}

// TODO: Wait for chip to come back and confirm version

return 0;
}
108 changes: 108 additions & 0 deletions protocol/dfu_hostcmd.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Code for performing a device-firmware update using host commands.

#ifndef _LIBHOTH_PROTOCOL_DFU_HOSTCMD_H_
#define _LIBHOTH_PROTOCOL_DFU_HOSTCMD_H_

#include <assert.h>
#include <stddef.h>
#include <stdint.h>

#include "protocol/host_cmd.h"
#include "transports/libhoth_device.h"

#ifdef __cplusplus
extern "C" {
#endif

#define HOTH_CMD_DFU_WRITE 0x3e4f
#define HOTH_CMD_DFU_COMPLETE 0x3e50

enum {
HOTH_DFU_TARGET_EARLGREY_FW_UPDATE = 0x77664745,
};

struct hoth_dfu_nonce {
uint32_t low;
uint32_t high;
};

struct hoth_dfu_session_id {
/// The nonce should be randomly generated at the start of the session; it
/// is used to detect simultaneous use of the DFU functionality.
struct hoth_dfu_nonce nonce;

// One of HOTH_DFU_TARGET_* constants
uint32_t target;
};
static_assert(offsetof(struct hoth_dfu_session_id, nonce) == 0);
static_assert(offsetof(struct hoth_dfu_session_id, target) == 8);
static_assert(sizeof(struct hoth_dfu_session_id) == 12);

enum {
// Set if this is the first chunk of a new DFU session.
HOTH_DFU_WRITE_FLAGS_NEW_SESSION = (1 << 0)
};

struct hoth_dfu_write_request_header {
struct hoth_dfu_session_id session_id;

// combination of HOTH_DFU_WRITE_FLAGS_*
uint32_t flags;
};
static_assert(offsetof(struct hoth_dfu_write_request_header, session_id) == 0);
static_assert(offsetof(struct hoth_dfu_write_request_header, flags) == 12);
static_assert(sizeof(struct hoth_dfu_write_request_header) == 16);

enum {
// Set ifwe should restart the chip into the new firmware.
HOTH_DFU_COMPLETE_FLAGS_COLD_RESTART = (1 << 0),

// Set if we should do a warm restart the chip into the new firmware.
HOTH_DFU_COMPLETE_FLAGS_WARM_RESTART = (1 << 1),
};

struct hoth_dfu_complete_request {
struct hoth_dfu_session_id session_id;

// combination of HOTH_DFU_COMPLETE_FLAGS_*
uint32_t flags;
};
static_assert(offsetof(struct hoth_dfu_complete_request, session_id) == 0);
static_assert(offsetof(struct hoth_dfu_complete_request, flags) == 12);
static_assert(sizeof(struct hoth_dfu_complete_request) == 16);

/**
* @brief Update the firmware on the device using DFU host commands.
*
* This function sends the provided firmware image to the device in chunks
* and then sends a DFU complete command to finalize the update.
*
* @param dev The libhoth device handle.
* @param image A pointer to the firmware image data.
* @param image_size The size of the firmware image in bytes.
* @param complete_flags Flags to be passed to the DFU complete command,
* e.g., HOTH_DFU_COMPLETE_FLAGS_COLD_RESTART.
* @return 0 on success, -1 on failure.
*/
int libhoth_dfu_update(struct libhoth_device* dev, const uint8_t* image,
size_t image_size, uint32_t complete_flags);

#ifdef __cplusplus
}
#endif

#endif // _LIBHOTH_PROTOCOL_DFU_HOSTCMD_H_
1 change: 1 addition & 0 deletions protocol/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ protocol_srcs = [
'key_rotation.c',
'secure_boot.c',
'command_version.c',
'dfu_hostcmd.c',
]

incdir = include_directories('..')
Expand Down