Files
pebble/src/fw/services/prf/accessory/accessory_imaging.c
2025-01-27 11:38:16 -08:00

607 lines
18 KiB
C

/*
* Copyright 2024 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.
*/
#include "accessory_imaging.h"
#include "console/prompt.h"
#include "drivers/accessory.h"
#include "drivers/flash.h"
#include "flash_region/flash_region.h"
#include "kernel/core_dump.h"
#include "kernel/core_dump_private.h"
#include "mfg/mfg_mode/mfg_factory_mode.h"
#include "resource/resource_storage_flash.h"
#include "services/common/new_timer/new_timer.h"
#include "services/common/system_task.h"
#include "services/prf/accessory/accessory_manager.h"
#include "system/bootbits.h"
#include "system/logging.h"
#include "system/passert.h"
#include "system/reset.h"
#include "util/crc32.h"
#include "util/hdlc.h"
#include "util/attributes.h"
#include "util/math.h"
#include <stdint.h>
#include <inttypes.h>
#include <stdbool.h>
#define TIMEOUT_MS (3000)
#define VERSION (1)
#define NUM_RX_BUFFERS (3)
#define MAX_DATA_LENGTH (2048)
#define CHECKSUM_LENGTH (4)
#define MAX_FRAME_LENGTH (MAX_DATA_LENGTH + sizeof(ImagingHeader) + CHECKSUM_LENGTH)
// flags
#define FLAG_IS_SERVER (1 << 0)
#define FLAG_VERSION (VERSION << 1)
// opcodes
#define OPCODE_PING (0x01)
#define OPCODE_DISCONNECT (0x02)
#define OPCODE_RESET (0x03)
#define OPCODE_FLASH_GEOMETRY (0x11)
#define OPCODE_FLASH_ERASE (0x12)
#define OPCODE_FLASH_WRITE (0x13)
#define OPCODE_FLASH_CRC (0x14)
#define OPCODE_FLASH_FINALIZE (0x15)
#define OPCODE_FLASH_READ (0x16)
// flash regions
#define REGION_PRF (0x01)
#define REGION_RESOURCES (0x02)
#define REGION_FW_SCRATCH (0x03)
#define REGION_PFS (0x04)
#define REGION_COREDUMP (0x05)
// flash read flags
#define FLASH_READ_FLAG_ALL_SAME (1 << 0)
typedef struct PACKED {
uint8_t flags;
uint8_t opcode;
} ImagingHeader;
typedef struct PACKED {
uint8_t region;
} FlashGeometryRequest;
typedef struct PACKED {
uint8_t region;
uint32_t address;
uint32_t length;
} FlashGeometryResponse;
typedef struct PACKED {
uint32_t address;
uint32_t length;
} FlashEraseRequest;
typedef struct PACKED {
uint32_t address;
uint32_t length;
uint8_t complete;
} FlashEraseResponse;
typedef struct PACKED {
uint32_t address;
uint8_t data[];
} FlashWriteRequest;
typedef struct PACKED {
uint32_t address;
uint32_t length;
} FlashReadRequest;
typedef struct PACKED {
uint32_t address;
uint32_t length;
} FlashCRCRequest;
typedef struct PACKED {
uint32_t address;
uint32_t length;
uint32_t crc;
} FlashCRCResponse;
typedef struct PACKED {
uint8_t region;
} FlashFinalizeRequest;
typedef FlashFinalizeRequest FlashFinalizeResponse; // they are currently the same
typedef struct {
bool is_free;
bool is_valid;
HdlcStreamingContext hdlc_ctx;
uint32_t index;
union {
struct PACKED {
ImagingHeader header;
uint8_t payload[MAX_DATA_LENGTH];
uint32_t checksum;
} frame;
uint8_t data[MAX_FRAME_LENGTH];
};
uint32_t checksum;
} ReceiveBuffer;
static bool s_enabled;
static TimerID s_timeout_timer;
static ReceiveBuffer s_buffers[NUM_RX_BUFFERS];
static ReceiveBuffer *s_curr_buf;
static bool s_flash_erase_in_progress;
static int s_no_buffer_count;
static int s_dropped_char_count;
static void prv_timeout_timer_cb(void *context);
// Helper functions
////////////////////////////////////////////////////////////////////
static void prv_reset_buffer(ReceiveBuffer *buffer) {
hdlc_streaming_decode_reset(&buffer->hdlc_ctx);
buffer->index = 0;
buffer->checksum = CRC32_INIT;
buffer->is_valid = true;
buffer->is_free = true;
}
// Start / stop
////////////////////////////////////////////////////////////////////
static void prv_start(void) {
s_curr_buf = NULL;
s_no_buffer_count = 0;
s_dropped_char_count = 0;
for (int i = 0; i < NUM_RX_BUFFERS; ++i) {
prv_reset_buffer(&s_buffers[i]);
}
accessory_manager_set_state(AccessoryInputStateImaging);
accessory_use_dma(true);
s_timeout_timer = new_timer_create();
new_timer_start(s_timeout_timer, TIMEOUT_MS, prv_timeout_timer_cb, NULL, 0 /* flags */);
PBL_LOG(LOG_LEVEL_DEBUG, "Starting accessory imaging");
}
static void prv_stop(void *context) {
if (s_no_buffer_count > 0) {
PBL_LOG(LOG_LEVEL_ERROR, "Ran out of buffers %d times and dropped %d bytes while imaging",
s_no_buffer_count, s_dropped_char_count);
}
flash_prf_set_protection(true);
accessory_use_dma(false);
accessory_manager_set_state(AccessoryInputStateMfg);
new_timer_delete(s_timeout_timer);
PBL_LOG(LOG_LEVEL_DEBUG, "Stopping accessory imaging");
}
static void prv_timeout_timer_cb(void *context) {
system_task_add_callback(prv_stop, NULL);
}
// Sending
////////////////////////////////////////////////////////////////////
static void prv_encode_and_send_data(const void *data, uint32_t length) {
const uint8_t *data_bytes = data;
for (uint32_t i = 0; i < length; i++) {
uint8_t byte = data_bytes[i];
if (hdlc_encode(&byte)) {
accessory_send_byte(HDLC_ESCAPE);
}
accessory_send_byte(byte);
}
}
static void prv_send_frame(uint8_t opcode, const void *payload, uint32_t length) {
accessory_disable_input();
accessory_send_byte(HDLC_FLAG);
// send the header
const ImagingHeader header = {
.flags = FLAG_IS_SERVER | FLAG_VERSION,
.opcode = opcode
};
prv_encode_and_send_data(&header, sizeof(header));
// send the pyaload
prv_encode_and_send_data(payload, length);
// send the checksum
uint32_t checksum = CRC32_INIT;
checksum = crc32(checksum, &header, sizeof(header));
if (payload && length) {
checksum = crc32(checksum, payload, length);
}
prv_encode_and_send_data(&checksum, sizeof(checksum));
accessory_send_byte(HDLC_FLAG);
accessory_enable_input();
}
// Request processing
////////////////////////////////////////////////////////////////////
static void prv_erase_complete(void *ignored, status_t result) {
s_flash_erase_in_progress = false;
}
static bool prv_is_erased(uint32_t addr, uint32_t length) {
const uint32_t sectors_to_erase = (length + SECTOR_SIZE_BYTES - 1) / SECTOR_SIZE_BYTES;
for (uint32_t sector = 0; sector < sectors_to_erase; sector++) {
if (!flash_sector_is_erased(sector * SECTOR_SIZE_BYTES + addr)) {
return false;
}
}
return true;
}
static void prv_handle_ping_request(const void *payload, uint32_t length) {
// echo it back
prv_send_frame(OPCODE_PING, payload, length);
}
static void prv_handle_disconnect_request(const void *payload, uint32_t length) {
if (length) {
// should be 0
PBL_LOG(LOG_LEVEL_ERROR, "Invalid length (%"PRIu32")", length);
return;
}
prv_send_frame(OPCODE_DISCONNECT, NULL, 0);
prv_stop(NULL);
}
static void prv_handle_reset_request(const void *payload, uint32_t length) {
if (length) {
// should be 0
PBL_LOG(LOG_LEVEL_ERROR, "Invalid length (%"PRIu32")", length);
return;
}
PBL_LOG(LOG_LEVEL_WARNING, "Got reset request");
prv_send_frame(OPCODE_RESET, NULL, 0);
prv_stop(NULL);
system_reset();
}
static bool prv_coredump_flash_base(uint32_t *addr, uint32_t *size) {
CoreDumpFlashHeader flash_hdr;
CoreDumpFlashRegionHeader region_hdr;
uint32_t max_last_used = 0;
uint32_t base_address;
uint32_t last_used_idx = 0;
// First, see if the flash header has been put in place
flash_read_bytes((uint8_t *)&flash_hdr, CORE_DUMP_FLASH_START, sizeof(flash_hdr));
if ((flash_hdr.magic != CORE_DUMP_FLASH_HDR_MAGIC) ||
(flash_hdr.unformatted == CORE_DUMP_ALL_UNFORMATTED)) {
return false;
}
// Find the region with the highest last_used count
for (unsigned int i = 0; i < CORE_DUMP_MAX_IMAGES; i++) {
if (flash_hdr.unformatted & (1 << i)) {
continue;
}
base_address = core_dump_get_slot_address(i);
flash_read_bytes((uint8_t *)&region_hdr, base_address, sizeof(region_hdr));
if (region_hdr.last_used > max_last_used) {
max_last_used = region_hdr.last_used;
last_used_idx = i;
}
}
if (max_last_used == 0) {
return false;
}
*addr = core_dump_get_slot_address(last_used_idx);
if (core_dump_size(*addr, size) != S_SUCCESS) {
return false;
}
*addr += sizeof(CoreDumpFlashRegionHeader);
return true;
}
static void prv_handle_flash_geometry_request(const void *payload, uint32_t length) {
if (length != sizeof(FlashGeometryRequest)) {
PBL_LOG(LOG_LEVEL_ERROR, "Invalid length (%"PRIu32")", length);
return;
}
const FlashGeometryRequest *request = payload;
FlashGeometryResponse response = {
.region = request->region
};
if (request->region == REGION_PRF) {
// assume we're about to write to this region, so unlock it
flash_prf_set_protection(false);
response.address = FLASH_REGION_SAFE_FIRMWARE_BEGIN;
response.length = FLASH_REGION_SAFE_FIRMWARE_END - response.address;
} else if (request->region == REGION_RESOURCES) {
const SystemResourceBank *bank = resource_storage_flash_get_unused_bank();
response.address = bank->begin;
response.length = bank->end - response.address;
} else if (request->region == REGION_FW_SCRATCH) {
response.address = FLASH_REGION_FIRMWARE_SCRATCH_BEGIN;
response.length = FLASH_REGION_FIRMWARE_SCRATCH_END - response.address;
} else if (request->region == REGION_PFS) {
response.address = FLASH_REGION_FILESYSTEM_BEGIN;
response.length = FLASH_REGION_FILESYSTEM_END - response.address;
} else if (request->region == REGION_COREDUMP) {
if (!prv_coredump_flash_base(&response.address, &response.length)) {
response.address = 0;
response.length = 0;
}
} else {
PBL_LOG(LOG_LEVEL_ERROR, "Invalid region (%"PRIu8")", request->region);
}
prv_send_frame(OPCODE_FLASH_GEOMETRY, &response, sizeof(response));
}
static void prv_handle_flash_erase_request(const void *payload, uint32_t length) {
if (length != sizeof(FlashEraseRequest)) {
PBL_LOG(LOG_LEVEL_ERROR, "Invalid length (%"PRIu32")", length);
return;
}
const FlashEraseRequest *request = payload;
FlashEraseResponse response = {
.address = request->address,
.length = request->length
};
bool start_erase = false;
if (s_flash_erase_in_progress) {
response.complete = 0;
} else if (prv_is_erased(request->address, request->length)) {
response.complete = 1;
} else {
response.complete = 0;
start_erase = true;
}
prv_send_frame(OPCODE_FLASH_ERASE, &response, sizeof(response));
// start the erase after sending the response
if (start_erase) {
uint32_t end_address = request->address + request->length;
s_flash_erase_in_progress = true;
flash_erase_optimal_range(
request->address, request->address, end_address,
(end_address + SECTOR_SIZE_BYTES - 1) & SECTOR_ADDR_MASK,
prv_erase_complete, NULL);
}
}
static void prv_handle_flash_write_request(const void *payload, uint32_t length) {
if (length < sizeof(FlashWriteRequest)) {
PBL_LOG(LOG_LEVEL_ERROR, "Invalid length (%"PRIu32")", length);
return;
}
const FlashWriteRequest *request = payload;
length -= offsetof(FlashWriteRequest, data);
flash_write_bytes(request->data, request->address, length);
}
static void prv_handle_flash_read_request(const void *payload, uint32_t length) {
if (length < sizeof(FlashReadRequest)) {
PBL_LOG(LOG_LEVEL_ERROR, "Invalid length (%"PRIu32")", length);
return;
}
const FlashReadRequest *request = payload;
if (request->length > MAX_DATA_LENGTH) {
PBL_LOG(LOG_LEVEL_ERROR, "Invalid request length (%"PRIu32")", request->length);
}
// leave 1 byte at the start for flags
static uint8_t buffer[1 + MAX_DATA_LENGTH];
flash_read_bytes(&buffer[1], request->address, request->length);
bool is_all_same = true;
uint8_t same_byte = buffer[1];
for (uint32_t i = 1; i < request->length; i++) {
if (buffer[i + 1] != same_byte) {
is_all_same = false;
break;
}
}
// As an optimization, if all the bytes are the same, we set a flag and just send a single byte.
buffer[0] = is_all_same ? FLASH_READ_FLAG_ALL_SAME : 0; // flags
const uint32_t frame_length = is_all_same ? 2 : request->length + 1;
prv_send_frame(OPCODE_FLASH_READ, buffer, frame_length);
}
static void prv_handle_flash_crc_request(const void *payload, uint32_t length) {
// there can be 1 or more payloads
if (!length || (length % sizeof(FlashCRCRequest))) {
PBL_LOG(LOG_LEVEL_ERROR, "Invalid length (%"PRIu32")", length);
return;
}
const FlashCRCRequest *request = payload;
const uint32_t num_entries = length / sizeof(FlashCRCRequest);
// this is just static cause it's potentially too big to put on the stack
static FlashCRCResponse response[MAX_DATA_LENGTH / sizeof(FlashCRCResponse)];
for (uint32_t i = 0; i < num_entries; i++) {
const FlashCRCRequest *entry = &request[i];
response[i] = (FlashCRCResponse) {
.address = entry->address,
.length = entry->length,
.crc = flash_crc32(entry->address, entry->length)
};
}
prv_send_frame(OPCODE_FLASH_CRC, &response, sizeof(FlashCRCResponse) * num_entries);
}
static void prv_handle_flash_finalize_request(const void *payload, uint32_t length) {
if (length != sizeof(FlashFinalizeRequest)) {
PBL_LOG(LOG_LEVEL_ERROR, "Invalid length (%"PRIu32")", length);
return;
}
const FlashFinalizeRequest *request = payload;
FlashFinalizeResponse response = {
.region = request->region
};
if (request->region == REGION_PRF) {
flash_prf_set_protection(true);
} else if (request->region == REGION_RESOURCES) {
boot_bit_set(BOOT_BIT_NEW_SYSTEM_RESOURCES_AVAILABLE);
} else if (request->region == REGION_FW_SCRATCH) {
boot_bit_set(BOOT_BIT_NEW_FW_AVAILABLE);
} else if (request->region == REGION_PFS) {
// Do nothing!
} else if (request->region == REGION_COREDUMP) {
// Do nothing!
} else {
PBL_LOG(LOG_LEVEL_ERROR, "Invalid region (%"PRIu8")", request->region);
}
prv_send_frame(OPCODE_FLASH_FINALIZE, &response, sizeof(response));
}
static void prv_process_frame(void *context) {
ReceiveBuffer *buf = context;
const ImagingHeader *header = &buf->frame.header;
const void *payload = buf->frame.payload;
const uint32_t payload_length = buf->index - sizeof(ImagingHeader) - CHECKSUM_LENGTH;
PBL_ASSERTN(payload_length <= MAX_DATA_LENGTH);
// sanity check
if (header->flags & FLAG_IS_SERVER) {
PBL_LOG(LOG_LEVEL_ERROR, "Got frame from server (loopback?)");
prv_reset_buffer(buf);
return;
}
// reset the timeout timer
new_timer_start(s_timeout_timer, TIMEOUT_MS, prv_timeout_timer_cb, NULL, 0 /* flags */);
// look at the opcode and handle this message
if (header->opcode == OPCODE_PING) {
prv_handle_ping_request(payload, payload_length);
} else if (header->opcode == OPCODE_DISCONNECT) {
prv_handle_disconnect_request(payload, payload_length);
} else if (header->opcode == OPCODE_RESET) {
prv_handle_reset_request(payload, payload_length);
} else if (header->opcode == OPCODE_FLASH_GEOMETRY) {
prv_handle_flash_geometry_request(payload, payload_length);
} else if (header->opcode == OPCODE_FLASH_ERASE) {
prv_handle_flash_erase_request(payload, payload_length);
} else if (header->opcode == OPCODE_FLASH_WRITE) {
prv_handle_flash_write_request(payload, payload_length);
} else if (header->opcode == OPCODE_FLASH_READ) {
prv_handle_flash_read_request(payload, payload_length);
} else if (header->opcode == OPCODE_FLASH_CRC) {
prv_handle_flash_crc_request(payload, payload_length);
} else if (header->opcode == OPCODE_FLASH_FINALIZE) {
prv_handle_flash_finalize_request(payload, payload_length);
} else {
PBL_LOG(LOG_LEVEL_ERROR, "Got unexpected opcode (0x%x)", header->opcode);
}
prv_reset_buffer(buf);
}
// Receiving (ISR-based)
////////////////////////////////////////////////////////////////////
static bool prv_handle_data(uint8_t data) {
bool should_context_switch = false;
bool hdlc_err;
bool should_store;
bool is_complete = hdlc_streaming_decode(&s_curr_buf->hdlc_ctx, &data, &should_store, &hdlc_err);
if (hdlc_err) {
s_curr_buf->is_valid = false;
} else if (is_complete) {
if (s_curr_buf->is_valid && s_curr_buf->checksum == CRC32_RESIDUE && s_curr_buf->index) {
// queue up processing of this frame and clear s_curr_buf so we'll switch to a new one
system_task_add_callback_from_isr(prv_process_frame, s_curr_buf, &should_context_switch);
} else {
prv_reset_buffer(s_curr_buf);
}
s_curr_buf = NULL;
} else if (should_store && s_curr_buf->is_valid) {
if (s_curr_buf->index < MAX_FRAME_LENGTH) {
// store this byte
s_curr_buf->data[(s_curr_buf->index)++] = data;
s_curr_buf->checksum = crc32(s_curr_buf->checksum, &data, 1);
} else {
// too long!
s_curr_buf->is_valid = false;
}
}
return should_context_switch;
}
bool accessory_imaging_handle_char(char c) {
static bool has_no_buffer = false;
if (!s_curr_buf) {
// find a buffer to write into
for (int i = 0; i < NUM_RX_BUFFERS; ++i) {
if (s_buffers[i].is_free) {
s_buffers[i].is_free = false;
s_curr_buf = &s_buffers[i];
break;
}
}
if (!s_curr_buf) {
// no available buffer :(
if (!has_no_buffer) {
s_no_buffer_count++;
}
has_no_buffer = true;
s_dropped_char_count++;
return false;
}
}
has_no_buffer = false;
return prv_handle_data((uint8_t)c);
}
// Other exported functions
////////////////////////////////////////////////////////////////////
void accessory_imaging_enable(void) {
s_enabled = true;
}
void command_accessory_imaging_start(void) {
if (!s_enabled) {
prompt_send_response("Command not available.");
}
prv_start();
}