Coverage Report

Created: 2026-08-31 07:11

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/hothd/payload_update.cpp
Line
Count
Source
1
// Copyright 2024 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
#include "payload_update.hpp"
16
17
#include "google3/host_commands.h"
18
19
#include "message_util.hpp"
20
#include "sys.hpp"
21
22
#include <fcntl.h>
23
#include <unistd.h>
24
25
#include <boost/endian/conversion.hpp>
26
#include <stdplus/print.hpp>
27
#include <stdplus/raw.hpp>
28
#include <xyz/openbmc_project/Control/Hoth/error.hpp>
29
30
#include <array>
31
#include <cstdint>
32
#include <format>
33
#include <memory>
34
#include <span>
35
#include <string>
36
#include <vector>
37
38
namespace google
39
{
40
namespace hoth
41
{
42
namespace internal
43
{
44
45
enum hoth_status : uint16_t
46
{
47
    /*
48
      There is no update payload pending confirmation
49
    */
50
    HOTH_PAYLOAD_UPDATE_CONFIRM_NO_PENDING_PAYLOAD = 0xD004,
51
};
52
53
using sdbusplus::error::xyz::openbmc_project::control::hoth::ResponseFailure;
54
55
void PayloadUpdateImpl::initiate() const
56
2.38k
{
57
2.38k
    sendCommand(PAYLOAD_UPDATE_INITIATE);
58
2.38k
}
59
60
void PayloadUpdateImpl::erase(const uint32_t offset, const uint32_t size) const
61
734
{
62
734
    sendCommand(PAYLOAD_UPDATE_ERASE, offset, size);
63
734
}
64
65
bool PayloadUpdateImpl::findDescriptor(const std::string& path,
66
                                       uint32_t* desc_offset) const
67
2.92k
{
68
2.92k
    Fd fd(sys->open(path.c_str(), O_RDONLY), sys);
69
70
2.92k
    if (*fd < 0)
71
0
    {
72
0
        (void)fd.release();
73
0
        throw errnoException(std::format("Failed to open file {}", path));
74
0
    }
75
76
2.92k
    auto seekRet = sys->lseek(*fd, 0, SEEK_END);
77
2.92k
    size_t fsize = seekRet;
78
79
2.92k
    if (seekRet < 0)
80
0
    {
81
0
        throw errnoException(std::format("Error seeking on file", path));
82
0
    }
83
84
2.92k
    uint64_t read_data = 0;
85
2.92k
    bool desc_found = false;
86
87
    // Find the nearest aligned address to the end of the file. We can't
88
    // assume that the file-size is aligned.
89
2.92k
    ssize_t offset = static_cast<ssize_t>(
90
2.92k
        (fsize / kImageDescriptorAlignment) * kImageDescriptorAlignment);
91
2.92k
    if (offset + sizeof(kDescriptorMagic) > fsize)
92
461
    {
93
461
        offset -= kImageDescriptorAlignment;
94
461
    }
95
96
4.09k
    for (; offset >= 0; offset -= kImageDescriptorAlignment)
97
3.17k
    {
98
3.17k
        auto seek = sys->lseek(*fd, offset, SEEK_SET);
99
3.17k
        if (seek < 0)
100
0
        {
101
0
            throw errnoException(std::format("Error seeking on file", path));
102
0
        }
103
104
3.17k
        auto actualReadSize = sys->read(*fd, &read_data, sizeof(read_data));
105
106
3.17k
        if (actualReadSize < 0)
107
0
        {
108
0
            throw errnoException(
109
0
                std::format("Failed to read from file {}", path));
110
0
        }
111
112
3.17k
        if (read_data == kDescriptorMagic)
113
2.01k
        {
114
2.01k
            if (desc_offset)
115
2.01k
            {
116
2.01k
                *desc_offset = offset;
117
2.01k
                desc_found = true;
118
2.01k
                break;
119
2.01k
            }
120
2.01k
        }
121
3.17k
    }
122
123
2.92k
    return desc_found;
124
2.92k
}
125
126
void PayloadUpdateImpl::eraseAndSendStaticWPRegions(const std::string& path,
127
                                                    uint32_t desc_offset) const
128
2.01k
{
129
2.01k
    Fd fd(sys->open(path.c_str(), O_RDONLY), sys);
130
131
2.01k
    if (*fd < 0)
132
0
    {
133
0
        (void)fd.release();
134
0
        throw errnoException(std::format("Failed to open file {}", path));
135
0
    }
136
137
2.01k
    struct image_descriptor descriptor;
138
139
2.01k
    if (sys->lseek(*fd, desc_offset, SEEK_SET) < 0)
140
0
    {
141
0
        throw errnoException(std::format("Error seeking on file", path));
142
0
    }
143
2.01k
    auto actualReadSize = sys->read(*fd, &descriptor, sizeof(descriptor));
144
145
2.01k
    if (actualReadSize < 0)
146
0
    {
147
0
        throw errnoException(std::format("Failed to read from file {}", path));
148
0
    }
149
150
2.01k
    if (descriptor.descriptor_magic != DESCRIPTOR_MAGIC ||
151
2.01k
        descriptor.descriptor_offset != desc_offset ||
152
1.86k
        descriptor.region_count == 0)
153
266
    {
154
266
        throw errnoException(
155
266
            std::format("Invalid descriptor at offset {}", path));
156
266
    }
157
158
1.74k
    std::vector<struct image_region> image_regions(descriptor.region_count);
159
160
1.74k
    actualReadSize =
161
1.74k
        sys->read(*fd, image_regions.data(),
162
1.74k
                  sizeof(struct image_region) * descriptor.region_count);
163
1.74k
    if (actualReadSize < 0)
164
0
    {
165
0
        throw errnoException(std::format("Failed to read from file {}", path));
166
0
    }
167
168
    // Erase all the static and Write Protected regions of the payload
169
134k
    for (uint32_t i = 0; i < descriptor.region_count; i++)
170
132k
    {
171
132k
        if ((image_regions[i].region_attributes & IMAGE_REGION_STATIC) ||
172
130k
            (image_regions[i].region_attributes & IMAGE_REGION_WRITE_PROTECTED))
173
3.10k
        {
174
3.10k
            if (image_regions[i].region_size == 0 ||
175
3.05k
                (image_regions[i].region_size % kSectorSizeBytes) != 0)
176
501
            {
177
501
                throw errnoException(std::format("invalid staging area size"));
178
501
            }
179
2.59k
            uint32_t regionOffset = image_regions[i].region_offset;
180
2.59k
            uint32_t toEraseSize = image_regions[i].region_size;
181
25.3M
            while (toEraseSize >= kEraseChunkSizeBytes)
182
25.3M
            {
183
25.3M
                sendCommand(PAYLOAD_UPDATE_ERASE, regionOffset,
184
25.3M
                            kEraseChunkSizeBytes);
185
25.3M
                regionOffset += kEraseChunkSizeBytes;
186
25.3M
                toEraseSize -= kEraseChunkSizeBytes;
187
25.3M
            }
188
2.59k
            if (toEraseSize > 0)
189
1.84k
            {
190
1.84k
                sendCommand(PAYLOAD_UPDATE_ERASE, regionOffset, toEraseSize);
191
1.84k
            }
192
2.59k
        }
193
132k
    }
194
195
    // Read and send static and WP sections of the payload
196
28.0k
    for (uint32_t i = 0; i < descriptor.region_count; i++)
197
27.8k
    {
198
27.8k
        if ((image_regions[i].region_attributes & IMAGE_REGION_STATIC) ||
199
26.4k
            (image_regions[i].region_attributes & IMAGE_REGION_WRITE_PROTECTED))
200
1.75k
        {
201
1.75k
            uint32_t offset = image_regions[i].region_offset;
202
1.75k
            auto regionSize = image_regions[i].region_size;
203
1.75k
            size_t readSize = 0;
204
1.75k
            std::vector<uint8_t> readBuffer(max_packet_size);
205
206
1.75k
            if (sys->lseek(*fd, offset, SEEK_SET) != offset)
207
536
            {
208
536
                throw errnoException(
209
536
                    std::format("Failed to read file size {}", path));
210
536
            }
211
1.21k
            do
212
11.2k
            {
213
11.2k
                if (regionSize <= max_packet_size)
214
869
                {
215
869
                    readSize = regionSize;
216
869
                }
217
10.3k
                else
218
10.3k
                {
219
10.3k
                    readSize = max_packet_size;
220
10.3k
                }
221
222
11.2k
                if (!sys->read(*fd, readBuffer.data(), readSize))
223
422
                {
224
422
                    throw errnoException(
225
422
                        std::format("Failed to read from file {}", path));
226
422
                }
227
228
                // Decided not to use initializer list to create the span here
229
                // as actualReadSize is ssize_t and we'd need to static_cast
230
10.8k
                trimAndSend(std::span<uint8_t>(readBuffer.data(), readSize),
231
10.8k
                            offset);
232
10.8k
                offset += readSize;
233
10.8k
                regionSize -= readSize;
234
235
10.8k
            } while (regionSize); // actualReadSize will be 0 when EOF is
236
                                  // reached, exit loop
237
1.21k
        }
238
27.8k
    }
239
1.24k
}
240
241
void PayloadUpdateImpl::eraseAndSendStaticWP(const std::string& path) const
242
2.92k
{
243
2.92k
    uint32_t desc_offset = 0;
244
245
2.92k
    if (!findDescriptor(path, &desc_offset))
246
912
    {
247
912
        throw errnoException(std::format("No valid descriptor found"));
248
912
    }
249
250
2.01k
    eraseAndSendStaticWPRegions(path, desc_offset);
251
2.01k
}
252
253
void PayloadUpdateImpl::read(uint32_t offset, std::span<uint8_t> data) const
254
1.80k
{
255
1.80k
    std::vector<uint8_t> buf;
256
1.80k
    auto rsp = sendCommand(buf, PAYLOAD_UPDATE_READ, offset, data.size());
257
1.80k
    if (rsp.size() != data.size())
258
1.52k
    {
259
1.52k
        stdplus::print(
260
1.52k
            stderr,
261
1.52k
            "Payload command received a short response from Hoth `{} < {}`\n",
262
1.52k
            rsp.size(), data.size());
263
1.52k
        throw ResponseFailure();
264
1.52k
    }
265
287
    memcpy(data.data(), rsp.data(), data.size());
266
287
}
267
268
void PayloadUpdateImpl::verify() const
269
444
{
270
444
    payload_update_packet request = {};
271
444
    request.type = PAYLOAD_UPDATE_VERIFY_DESCRIPTOR;
272
444
    auto buf = hostCmd->sendCommand(
273
444
        EC_CMD_BOARD_SPECIFIC_BASE + EC_PRV_CMD_HOTH_PAYLOAD_UPDATE, 0,
274
444
        &request, sizeof(request));
275
444
    std::span<uint8_t> rsp = buf;
276
444
    auto& hdr = stdplus::raw::extractRef<RspHeader>(rsp);
277
278
    // In case PAYLOAD_UPDATE_VERIFY is not supported (EC_RES_ERROR),
279
    // we fall back to PAYLOAD_UPDATE_VERIFY instead.
280
444
    if (hdr.result == EC_RES_INVALID_PARAM || hdr.result == EC_RES_ERROR)
281
0
    {
282
0
        sendCommand(PAYLOAD_UPDATE_VERIFY);
283
0
        return;
284
0
    }
285
444
    if (hdr.result != EC_RES_SUCCESS)
286
0
    {
287
0
        stdplus::print(stderr, "VERIFY_DESCRIPTOR error: {:#x}\n",
288
0
                       static_cast<uint16_t>(hdr.result));
289
0
        throw ResponseFailure();
290
0
    }
291
444
}
292
293
payload_update_status PayloadUpdateImpl::getStatus() const
294
572
{
295
572
    std::vector<uint8_t> buf;
296
572
    std::span<const uint8_t> output =
297
572
        sendCommand(buf, PAYLOAD_UPDATE_GET_STATUS);
298
299
572
    return stdplus::raw::copyFrom<payload_update_status>(output);
300
572
}
301
302
void PayloadUpdateImpl::activate(Side side, Persistence persistence) const
303
540
{
304
540
    ActivateRequest request;
305
540
    request.header.offset = 0;
306
540
    request.header.len = sizeof(request.activate);
307
540
    request.header.type = PAYLOAD_UPDATE_ACTIVATE;
308
540
    request.activate.half = static_cast<uint8_t>(side);
309
540
    request.activate.make_persistent = static_cast<uint8_t>(persistence);
310
311
540
    sendCommand(
312
540
        {reinterpret_cast<const uint8_t*>(&request), sizeof(ActivateRequest)});
313
540
}
314
315
std::optional<payload_update_confirm_response> PayloadUpdateImpl::confirm(
316
    enum payload_update_confirm_option option, uint32_t timeout,
317
    uint64_t confirmation_cookie) const
318
687
{
319
687
    ConfirmRequest request;
320
687
    request.header.offset = 0;
321
687
    request.header.len = sizeof(request.confirm);
322
687
    request.header.type = PAYLOAD_UPDATE_CONFIRM;
323
687
    request.confirm.option = option;
324
687
    request.confirm.timeout_value = boost::endian::native_to_little(timeout);
325
687
    request.confirm.confirmation_cookie =
326
687
        boost::endian::native_to_little(confirmation_cookie);
327
328
687
    std::vector<uint8_t> buf = hostCmd->sendCommand(
329
687
        EC_CMD_BOARD_SPECIFIC_BASE + EC_PRV_CMD_HOTH_PAYLOAD_UPDATE, 0,
330
687
        &request, sizeof(request));
331
    // Extract the response header out when returning the span
332
687
    std::span<const uint8_t> output = buf;
333
687
    auto rsp = stdplus::raw::extract<RspHeader>(output);
334
687
    if (rsp.result == HOTH_PAYLOAD_UPDATE_CONFIRM_NO_PENDING_PAYLOAD)
335
0
    {
336
0
        return std::nullopt;
337
0
    }
338
339
687
    if (rsp.result != EC_RES_SUCCESS)
340
0
    {
341
0
        stdplus::print(
342
0
            stderr,
343
0
            "Payload confirm command received a bad response from Hoth {:#x}\n",
344
0
            static_cast<uint16_t>(rsp.result));
345
0
        throw ResponseFailure();
346
0
    }
347
348
687
    return stdplus::raw::copyFrom<payload_update_confirm_response>(output);
349
687
}
350
351
namespace
352
{
353
// Helper functions for PayloadUpdateImpl::send
354
size_t SizeToSkip(std::span<const uint8_t> data)
355
17.6k
{
356
17.6k
    size_t i;
357
285k
    for (i = 0; i < data.size(); i++)
358
285k
    {
359
285k
        if (data[i] != 0xff)
360
17.0k
        {
361
17.0k
            break;
362
17.0k
        }
363
285k
    }
364
17.6k
    return i;
365
17.6k
}
366
367
size_t SizeToSend(std::span<const uint8_t> data)
368
17.0k
{
369
17.0k
    size_t i;
370
110k
    for (i = data.size(); i > 0; i--)
371
110k
    {
372
110k
        if (data[i - 1] != 0xff)
373
17.0k
        {
374
17.0k
            break;
375
17.0k
        }
376
110k
    }
377
17.0k
    return i;
378
17.0k
}
379
} // namespace
380
381
void PayloadUpdateImpl::send(const std::string& path) const
382
900
{
383
900
    Fd fd(sys->open(path.c_str(), O_RDONLY), sys);
384
385
900
    if (*fd < 0)
386
0
    {
387
0
        (void)fd.release();
388
0
        throw errnoException(std::format("Failed to open file {}", path));
389
0
    }
390
391
900
    std::array<uint8_t, max_packet_size> readBuffer;
392
900
    uint32_t offset = 0;
393
900
    ssize_t actualReadSize = 0;
394
    // Read and send payload until we reach EOF of the given file
395
900
    do
396
7.72k
    {
397
7.72k
        actualReadSize = sys->read(*fd, readBuffer.data(), max_packet_size);
398
399
7.72k
        if (actualReadSize < 0)
400
0
        {
401
0
            throw errnoException(
402
0
                std::format("Failed to read from file {}", path));
403
0
        }
404
7.72k
        if (actualReadSize > 0)
405
6.82k
        {
406
            // Decided not to use initializer list to create the span here as
407
            // actualReadSize is ssize_t and we'd need to static_cast
408
6.82k
            trimAndSend(std::span<uint8_t>(readBuffer.data(), actualReadSize),
409
6.82k
                        offset);
410
6.82k
            offset += actualReadSize;
411
6.82k
        }
412
        // actualReadSize will be 0 when EOF is reached, exit loop
413
7.72k
    } while (actualReadSize);
414
900
}
415
416
/* Parse and send the given data after trimming 0xff from the front and back
417
 * - This works because PayloadUpdateImpl::Initiate erased the
418
 * EEPROM to 0xff.
419
 * - This is also more efficient than sending everything, as our
420
 * image binaries tend to have large sections of 0xff and we can
421
 * avoid sending those sections.
422
 */
423
void PayloadUpdateImpl::trimAndSend(std::span<const uint8_t> data,
424
                                    uint32_t globalOffset) const
425
17.6k
{
426
    // Allocate the payload buffer at "buffer".
427
    // "request" will point to the payload_update_packet section of the buffer
428
    // "request_payload" will point to the beginning of the payload
429
    // (after the payload_update_packet header).
430
17.6k
    std::array<uint8_t, sizeof(payload_update_packet) + max_packet_size> buffer;
431
17.6k
    payload_update_packet* request =
432
17.6k
        reinterpret_cast<payload_update_packet*>(buffer.data());
433
17.6k
    uint8_t* request_payload = &buffer[sizeof(payload_update_packet)];
434
435
17.6k
    const size_t dataSize = data.size();
436
17.6k
    if (dataSize > max_packet_size)
437
0
    {
438
0
        throw errnoException(
439
0
            "data to send cannot be bigger than max_packet_size");
440
0
    }
441
17.6k
    size_t localOffset = SizeToSkip(data);
442
17.6k
    if (localOffset >= dataSize)
443
532
    {
444
        // No need to send any payload
445
532
        return;
446
532
    }
447
17.0k
    size_t max_size_to_send = std::min(dataSize - localOffset, max_packet_size);
448
17.0k
    size_t size_to_send =
449
17.0k
        SizeToSend(data.subspan(localOffset, max_size_to_send));
450
451
    // Construct and send payload
452
17.0k
    request->offset = globalOffset + localOffset;
453
17.0k
    request->len = size_to_send;
454
17.0k
    request->type = PAYLOAD_UPDATE_CONTINUE;
455
17.0k
    memcpy(request_payload, &data[localOffset], size_to_send);
456
17.0k
    sendCommand({buffer.data(), sizeof(*request) + size_to_send});
457
17.0k
}
458
459
void PayloadUpdateImpl::sendCommand(uint8_t command, uint32_t offset,
460
                                    uint32_t len) const
461
25.3M
{
462
25.3M
    std::vector<uint8_t> buf;
463
25.3M
    (void)sendCommand(buf, command, offset, len);
464
25.3M
}
465
466
void PayloadUpdateImpl::sendCommand(std::span<const uint8_t> request) const
467
17.6k
{
468
17.6k
    std::vector<uint8_t> buf;
469
17.6k
    (void)sendCommand(buf, request);
470
17.6k
}
471
472
[[nodiscard]] std::span<const uint8_t> PayloadUpdateImpl::sendCommand(
473
    std::vector<uint8_t>& buf, uint8_t command, uint32_t offset,
474
    uint32_t len) const
475
25.3M
{
476
    // For most basic commands, request offset and length are zero
477
25.3M
    payload_update_packet request;
478
25.3M
    request.offset = offset;
479
25.3M
    request.len = len;
480
25.3M
    request.type = command;
481
25.3M
    return sendCommand(buf, {reinterpret_cast<const uint8_t*>(&request),
482
25.3M
                             sizeof(payload_update_packet)});
483
25.3M
}
484
485
[[nodiscard]] std::span<const uint8_t> PayloadUpdateImpl::sendCommand(
486
    std::vector<uint8_t>& buf, std::span<const uint8_t> request) const
487
25.3M
{
488
25.3M
    buf = hostCmd->sendCommand(
489
25.3M
        EC_CMD_BOARD_SPECIFIC_BASE + EC_PRV_CMD_HOTH_PAYLOAD_UPDATE, 0,
490
25.3M
        request.data(), request.size());
491
    // Extract the response header out when returning the span
492
25.3M
    std::span<const uint8_t> output = buf;
493
25.3M
    auto rsp = stdplus::raw::extract<RspHeader>(output);
494
25.3M
    if (rsp.result != EC_RES_SUCCESS)
495
0
    {
496
0
        stdplus::print(
497
0
            stderr, "Payload command received a bad response from Hoth {:#x}\n",
498
0
            static_cast<uint16_t>(rsp.result));
499
0
        throw ResponseFailure();
500
0
    }
501
502
25.3M
    return output;
503
25.3M
}
504
505
} // namespace internal
506
507
} // namespace hoth
508
509
} // namespace google