mirror of
https://github.com/google/pebble.git
synced 2025-11-28 02:02:23 -05:00
607 lines
18 KiB
C
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 *)®ion_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();
|
|
}
|