Skip to content

Commit 6ab0c08

Browse files
committed
Add basic DFU host-command support.
1 parent 5fbf362 commit 6ab0c08

File tree

9 files changed

+346
-0
lines changed

9 files changed

+346
-0
lines changed

examples/BUILD

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ cc_binary(
8787
"htool_console.h",
8888
"htool_constants.h",
8989
"htool_dbus.c",
90+
"htool_dfu.c",
91+
"htool_dfu.h",
9092
"htool_i2c.c",
9193
"htool_i2c.h",
9294
"htool_jtag.c",
@@ -135,6 +137,7 @@ cc_binary(
135137
"//protocol:authz_record",
136138
"//protocol:chipinfo",
137139
"//protocol:controlled_storage",
140+
"//protocol:dfu_hostcmd",
138141
"//protocol:hello",
139142
"//protocol:host_cmd",
140143
"//protocol:i2c",

examples/htool.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include "htool_authz_command.h"
3333
#include "htool_cmd.h"
3434
#include "htool_console.h"
35+
#include "htool_dfu.h"
3536
#include "htool_i2c.h"
3637
#include "htool_jtag.h"
3738
#include "htool_key_rotation.h"
@@ -929,6 +930,18 @@ static const struct htool_cmd CMDS[] = {
929930
{HTOOL_POSITIONAL, .name = "source-file"}, {}},
930931
.func = htool_payload_info,
931932
},
933+
{
934+
.verbs = (const char*[]){"dfu", "update", NULL},
935+
.desc = "Directly install a PIE-RoT fwupdate.",
936+
.params =
937+
(const struct htool_param[]){
938+
{HTOOL_POSITIONAL, .name = "fwupdate-file",
939+
.desc = "A .fwupdate file compatible with this device."},
940+
{HTOOL_FLAG_VALUE, .name = "reset",
941+
.desc = "warm, cold, or none", .default_value = "warm"},
942+
{}},
943+
.func = htool_dfu_update,
944+
},
932945
{.verbs = (const char*[]){"flash_spi_info", NULL},
933946
.desc = "Get SPI NOR flash info.",
934947
.params = (const struct htool_param[]){{}},

examples/htool_dfu.c

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
2+
#include "htool_dfu.h"
3+
4+
#include <errno.h>
5+
#include <fcntl.h>
6+
#include <stdint.h>
7+
#include <stdio.h>
8+
#include <string.h>
9+
#include <sys/mman.h>
10+
#include <sys/stat.h>
11+
#include <sys/types.h>
12+
#include <unistd.h>
13+
14+
#include "htool.h"
15+
#include "htool_cmd.h"
16+
#include "protocol/dfu_hostcmd.h"
17+
18+
int htool_dfu_update(const struct htool_invocation* inv) {
19+
struct libhoth_device* dev = htool_libhoth_device();
20+
if (!dev) {
21+
return -1;
22+
}
23+
24+
uint32_t complete_flags = 0;
25+
const char* reset_arg;
26+
if (htool_get_param_string(inv, "reset", &reset_arg)) {
27+
return -1;
28+
}
29+
if (strcmp(reset_arg, "warm") == 0) {
30+
complete_flags |= HOTH_DFU_COMPLETE_FLAGS_WARM_RESTART;
31+
} else if (strcmp(reset_arg, "cold") == 0) {
32+
complete_flags |= HOTH_DFU_COMPLETE_FLAGS_COLD_RESTART;
33+
} else if (strcmp(reset_arg, "none") == 0) {
34+
// No flags needed
35+
} else {
36+
fprintf(
37+
stderr,
38+
"Invalid value for --reset: %s. Must be 'warm', 'cold', or 'none'.\n",
39+
reset_arg);
40+
return -1;
41+
}
42+
43+
const char* fwupdate_file;
44+
if (htool_get_param_string(inv, "fwupdate-file", &fwupdate_file)) {
45+
return -1;
46+
}
47+
48+
int fd = open(fwupdate_file, O_RDONLY, 0);
49+
if (fd == -1) {
50+
fprintf(stderr, "Error opening file %s: %s\n", fwupdate_file,
51+
strerror(errno));
52+
return -1;
53+
}
54+
55+
int retval = -1;
56+
57+
struct stat statbuf;
58+
if (fstat(fd, &statbuf)) {
59+
fprintf(stderr, "fstat error: %s\n", strerror(errno));
60+
goto cleanup;
61+
}
62+
if (statbuf.st_size > SIZE_MAX) {
63+
fprintf(stderr, "file too large\n");
64+
goto cleanup;
65+
}
66+
67+
uint8_t* image = mmap(NULL, statbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
68+
if (image == MAP_FAILED) {
69+
fprintf(stderr, "mmap error: %s\n", strerror(errno));
70+
goto cleanup;
71+
}
72+
73+
if (libhoth_dfu_update(dev, image, statbuf.st_size, complete_flags) != 0) {
74+
fprintf(stderr, "DFU update failed.\n");
75+
goto cleanup2;
76+
}
77+
retval = 0;
78+
79+
cleanup2:
80+
int ret = munmap(image, statbuf.st_size);
81+
if (ret != 0) {
82+
fprintf(stderr, "munmap error: %d\n", ret);
83+
}
84+
85+
cleanup:
86+
ret = close(fd);
87+
if (ret != 0) {
88+
fprintf(stderr, "close error: %d\n", ret);
89+
}
90+
return retval;
91+
}

examples/htool_dfu.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef LIBHOTH_EXAMPLES_HTOOL_DFU_H_
16+
#define LIBHOTH_EXAMPLES_HTOOL_DFU_H_
17+
18+
#ifdef __cplusplus
19+
extern "C" {
20+
#endif
21+
22+
struct htool_invocation;
23+
24+
int htool_dfu_update(const struct htool_invocation* inv);
25+
26+
#ifdef __cplusplus
27+
}
28+
#endif
29+
30+
#endif // LIBHOTH_EXAMPLES_HTOOL_DFU_H_

examples/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ executable(
2323
'htool_cmd.c',
2424
'htool_console.c',
2525
'htool_dbus.c',
26+
'htool_dfu.c',
2627
'htool_i2c.c',
2728
'htool_jtag.c',
2829
'htool_mtd.c',

protocol/BUILD

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,3 +414,13 @@ cc_test(
414414
"@googletest//:gtest_main",
415415
],
416416
)
417+
418+
cc_library(
419+
name = "dfu_hostcmd",
420+
srcs = ["dfu_hostcmd.c"],
421+
hdrs = ["dfu_hostcmd.h"],
422+
deps = [
423+
":host_cmd",
424+
"//transports:libhoth_device",
425+
],
426+
)

protocol/dfu_hostcmd.c

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
2+
#include "dfu_hostcmd.h"
3+
4+
#include <assert.h>
5+
#include <stdbool.h>
6+
#include <stddef.h>
7+
#include <stdio.h>
8+
#include <stdlib.h>
9+
#include <string.h>
10+
// for MIN()
11+
#include <sys/param.h>
12+
#include <sys/random.h>
13+
#include <time.h>
14+
#include <unistd.h>
15+
16+
#include "protocol/host_cmd.h"
17+
18+
static int generate_random_nonce(struct hoth_dfu_session_id* session_id) {
19+
ssize_t ret = getrandom(&session_id->nonce, sizeof(session_id->nonce), 0);
20+
if (ret == -1) {
21+
perror("getrandom");
22+
return -1;
23+
}
24+
return 0;
25+
}
26+
27+
int libhoth_dfu_update(struct libhoth_device* dev, const uint8_t* image,
28+
size_t image_size, uint32_t complete_flags) {
29+
struct hoth_dfu_session_id session_id = {
30+
.target = HOTH_DFU_TARGET_EARLGREY_FW_UPDATE,
31+
};
32+
if (generate_random_nonce(&session_id) != 0) {
33+
fprintf(stderr, "Failed to generate random nonce.\n");
34+
return -1;
35+
}
36+
37+
struct {
38+
struct hoth_dfu_write_request_header hdr;
39+
uint8_t data[1000];
40+
} request = {.hdr = {.session_id = session_id}};
41+
static_assert(sizeof(request) == LIBHOTH_MAILBOX_SIZE - 8);
42+
43+
size_t bytes_sent = 0;
44+
while (bytes_sent < image_size) {
45+
request.hdr.flags = bytes_sent == 0 ? HOTH_DFU_WRITE_FLAGS_NEW_SESSION : 0;
46+
47+
size_t chunk_len = MIN(sizeof(request.data), image_size - bytes_sent);
48+
memcpy(request.data, &image[bytes_sent], chunk_len);
49+
50+
size_t response_len;
51+
int ret = libhoth_hostcmd_exec(dev, HOTH_CMD_DFU_WRITE, 0, &request,
52+
sizeof(request.hdr) + chunk_len, NULL, 0,
53+
&response_len);
54+
if (ret != 0) {
55+
fprintf(stderr, "DFU write failed with error code: %d\n", ret);
56+
return -1;
57+
}
58+
if (response_len != 0) {
59+
fprintf(stderr, "DFU write expected 0 response bytes, got %zu\n",
60+
response_len);
61+
return -1;
62+
}
63+
bytes_sent += chunk_len;
64+
}
65+
66+
fprintf(stderr,
67+
"Completed sending fwupdate via DFU WRITE; sending DFU_COMPLETE to "
68+
"restart\n");
69+
70+
struct hoth_dfu_complete_request complete_request = {
71+
.session_id = session_id,
72+
.flags = complete_flags,
73+
};
74+
size_t response_len;
75+
int ret =
76+
libhoth_hostcmd_exec(dev, HOTH_CMD_DFU_COMPLETE, 0, &complete_request,
77+
sizeof(complete_request), NULL, 0, &response_len);
78+
if (ret != 0) {
79+
fprintf(stderr, "DFU complete failed with error code: %d\n", ret);
80+
return -1;
81+
}
82+
if (response_len != 0) {
83+
fprintf(stderr, "DFU complete expected 0 response bytes, got %zu\n",
84+
response_len);
85+
return -1;
86+
}
87+
88+
return 0;
89+
}

protocol/dfu_hostcmd.h

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Code for performing a device-firmware update using host commands.
16+
17+
#ifndef _LIBHOTH_PROTOCOL_DFU_HOSTCMD_H_
18+
#define _LIBHOTH_PROTOCOL_DFU_HOSTCMD_H_
19+
20+
#include <assert.h>
21+
#include <stddef.h>
22+
#include <stdint.h>
23+
24+
#include "protocol/host_cmd.h"
25+
#include "transports/libhoth_device.h"
26+
27+
#ifdef __cplusplus
28+
extern "C" {
29+
#endif
30+
31+
#define HOTH_CMD_DFU_WRITE 0x3e4f
32+
#define HOTH_CMD_DFU_COMPLETE 0x3e50
33+
34+
enum {
35+
HOTH_DFU_TARGET_EARLGREY_FW_UPDATE = 0x77664745,
36+
};
37+
38+
struct hoth_dfu_nonce {
39+
uint32_t low;
40+
uint32_t high;
41+
};
42+
43+
struct hoth_dfu_session_id {
44+
/// The nonce should be randomly generated at the start of the session; it
45+
/// is used to detect simultaneous use of the DFU functionality.
46+
struct hoth_dfu_nonce nonce;
47+
48+
// One of HOTH_DFU_TARGET_* constants
49+
uint32_t target;
50+
};
51+
static_assert(offsetof(struct hoth_dfu_session_id, nonce) == 0);
52+
static_assert(offsetof(struct hoth_dfu_session_id, target) == 8);
53+
static_assert(sizeof(struct hoth_dfu_session_id) == 12);
54+
55+
enum {
56+
// Set if this is the first chunk of a new DFU session.
57+
HOTH_DFU_WRITE_FLAGS_NEW_SESSION = (1 << 0)
58+
};
59+
60+
struct hoth_dfu_write_request_header {
61+
struct hoth_dfu_session_id session_id;
62+
63+
// combination of HOTH_DFU_WRITE_FLAGS_*
64+
uint32_t flags;
65+
};
66+
static_assert(offsetof(struct hoth_dfu_write_request_header, session_id) == 0);
67+
static_assert(offsetof(struct hoth_dfu_write_request_header, flags) == 12);
68+
static_assert(sizeof(struct hoth_dfu_write_request_header) == 16);
69+
70+
enum {
71+
// Set ifwe should restart the chip into the new firmware.
72+
HOTH_DFU_COMPLETE_FLAGS_COLD_RESTART = (1 << 0),
73+
74+
// Set if we should do a warm restart the chip into the new firmware.
75+
HOTH_DFU_COMPLETE_FLAGS_WARM_RESTART = (1 << 1),
76+
};
77+
78+
struct hoth_dfu_complete_request {
79+
struct hoth_dfu_session_id session_id;
80+
81+
// combination of HOTH_DFU_COMPLETE_FLAGS_*
82+
uint32_t flags;
83+
};
84+
static_assert(offsetof(struct hoth_dfu_complete_request, session_id) == 0);
85+
static_assert(offsetof(struct hoth_dfu_complete_request, flags) == 12);
86+
static_assert(sizeof(struct hoth_dfu_complete_request) == 16);
87+
88+
/**
89+
* @brief Update the firmware on the device using DFU host commands.
90+
*
91+
* This function sends the provided firmware image to the device in chunks
92+
* and then sends a DFU complete command to finalize the update.
93+
*
94+
* @param dev The libhoth device handle.
95+
* @param image A pointer to the firmware image data.
96+
* @param image_size The size of the firmware image in bytes.
97+
* @param complete_flags Flags to be passed to the DFU complete command,
98+
* e.g., HOTH_DFU_COMPLETE_FLAGS_COLD_RESTART.
99+
* @return 0 on success, -1 on failure.
100+
*/
101+
int libhoth_dfu_update(struct libhoth_device* dev, const uint8_t* image,
102+
size_t image_size, uint32_t complete_flags);
103+
104+
#ifdef __cplusplus
105+
}
106+
#endif
107+
108+
#endif // _LIBHOTH_PROTOCOL_DFU_HOSTCMD_H_

protocol/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ protocol_srcs = [
1818
'key_rotation.c',
1919
'secure_boot.c',
2020
'command_version.c',
21+
'dfu_hostcmd.c',
2122
]
2223

2324
incdir = include_directories('..')

0 commit comments

Comments
 (0)