Add embedded storage drivers and extend firmware metadata

This commit is contained in:
2026-09-27 01:44:59 +03:00
parent 795a1279b1
commit 2ad29e7ffd
52 changed files with 5801 additions and 3 deletions

View File

@@ -0,0 +1,165 @@
#include "spi_nor_command_service.h"
#include <string.h>
/* Только эти три opcode документированы одинаково для всех поддержанных NOR. */
#define SPI_NOR_DIAG_READ_DATA 0x03U
#define SPI_NOR_DIAG_READ_STATUS_1 0x05U
#define SPI_NOR_DIAG_READ_JEDEC_ID 0x9FU
/* HAL-коды переводятся в стабильные статусы диагностического протокола. */
static spi_nor_command_service_status_t map_io_status(
spi_nor_io_status_t status)
{
if (status == SPI_NOR_IO_OK) {
return SPI_NOR_COMMAND_SERVICE_OK;
}
if (status == SPI_NOR_IO_TIMEOUT) {
return SPI_NOR_COMMAND_SERVICE_TIMEOUT;
}
return SPI_NOR_COMMAND_SERVICE_IO_ERROR;
}
/* READ использует 24-битный big-endian адрес, как основное ядро SpiNor. */
static uint32_t read_address(const uint8_t tx[4])
{
return ((uint32_t)tx[1] << 16U) |
((uint32_t)tx[2] << 8U) |
(uint32_t)tx[3];
}
spi_nor_command_service_status_t spi_nor_command_service_validate(
const spi_nor_t *device,
const spi_nor_command_service_request_t *request)
{
uint8_t opcode;
if ((device == NULL) || (request == NULL) ||
(device->initialized == 0U) || (device->detected == 0U) ||
(device->io.transfer == NULL) || (device->io.chip_select == NULL)) {
return SPI_NOR_COMMAND_SERVICE_UNAVAILABLE;
}
if ((request->tx_length == 0U) ||
(request->tx_length > SPI_NOR_COMMAND_SERVICE_MAX_TX_BYTES) ||
(request->rx_length == 0U) ||
(request->rx_length > SPI_NOR_COMMAND_SERVICE_MAX_RX_BYTES)) {
return SPI_NOR_COMMAND_SERVICE_INVALID;
}
opcode = request->tx[0];
/* JEDEC возвращает ровно manufacturer/type/capacity для определения модели. */
if (opcode == SPI_NOR_DIAG_READ_JEDEC_ID) {
return ((request->tx_length == 1U) && (request->rx_length == 3U)) ?
SPI_NOR_COMMAND_SERVICE_OK : SPI_NOR_COMMAND_SERVICE_INVALID;
}
/* SR1 — единственный общий и однозначно read-only status всех трёх NOR. */
if (opcode == SPI_NOR_DIAG_READ_STATUS_1) {
return ((request->tx_length == 1U) && (request->rx_length == 1U)) ?
SPI_NOR_COMMAND_SERVICE_OK : SPI_NOR_COMMAND_SERVICE_INVALID;
}
/* Обычный READ допускает произвольный bounded RX, но требует весь адрес. */
if (opcode == SPI_NOR_DIAG_READ_DATA) {
uint32_t address;
if (request->tx_length != 4U) {
return SPI_NOR_COMMAND_SERVICE_INVALID;
}
address = read_address(request->tx);
if ((address >= device->info.capacity_bytes) ||
((uint32_t)request->rx_length >
(device->info.capacity_bytes - address))) {
return SPI_NOR_COMMAND_SERVICE_INVALID;
}
return SPI_NOR_COMMAND_SERVICE_OK;
}
/*
* Fail-closed default блокирует WREN, program/erase, status/protection
* writes, power-down/wake, reset и любой неизвестный opcode. Будущий
* изменяющий режим должен быть отдельным API, а не расширением allowlist.
*/
return SPI_NOR_COMMAND_SERVICE_BLOCKED;
}
spi_nor_command_service_status_t spi_nor_command_service_process(
spi_nor_t *device,
const spi_nor_command_service_request_t *request,
spi_nor_command_service_response_t *response)
{
spi_nor_command_service_status_t status;
spi_nor_io_status_t io_status;
uint8_t locked = 0U;
if (response == NULL) {
return SPI_NOR_COMMAND_SERVICE_INVALID;
}
/* Очистка запрещает повторное использование старого RX после любого отказа. */
memset(response, 0, sizeof(*response));
if (request != NULL) {
response->sequence = request->sequence;
response->tx_length = request->tx_length;
if (request->tx_length <= SPI_NOR_COMMAND_SERVICE_MAX_TX_BYTES) {
memcpy(response->tx_echo, request->tx, request->tx_length);
}
}
status = spi_nor_command_service_validate(device, request);
if (status != SPI_NOR_COMMAND_SERVICE_OK) {
response->status = status;
return status;
}
if (device->busy != 0U) {
response->status = SPI_NOR_COMMAND_SERVICE_BUSY;
return response->status;
}
if (device->io.lock != NULL) {
/* Mutex относится ко всей CS-транзакции, а не к отдельному transfer. */
io_status = device->io.lock(device->io.context,
device->config.lock_timeout_ms);
if (io_status != SPI_NOR_IO_OK) {
response->status = (io_status == SPI_NOR_IO_TIMEOUT) ?
SPI_NOR_COMMAND_SERVICE_TIMEOUT :
SPI_NOR_COMMAND_SERVICE_BUSY;
return response->status;
}
locked = 1U;
}
if (device->busy != 0U) {
if ((locked != 0U) && (device->io.unlock != NULL)) {
device->io.unlock(device->io.context);
}
response->status = SPI_NOR_COMMAND_SERVICE_BUSY;
return response->status;
}
/* Busy устанавливается после mutex и снимается до unlock основного устройства. */
device->busy = 1U;
response->status = SPI_NOR_COMMAND_SERVICE_BUSY;
/* CS освобождается безусловно после TX либо RX ошибки/тайм-аута. */
device->io.chip_select(device->io.context, 1U);
io_status = device->io.transfer(device->io.context, request->tx, NULL,
request->tx_length,
device->config.io_timeout_ms);
if (io_status == SPI_NOR_IO_OK) {
io_status = device->io.transfer(device->io.context, NULL, response->rx,
request->rx_length,
device->config.io_timeout_ms);
}
device->io.chip_select(device->io.context, 0U);
device->busy = 0U;
if ((locked != 0U) && (device->io.unlock != NULL)) {
device->io.unlock(device->io.context);
}
status = map_io_status(io_status);
if (status != SPI_NOR_COMMAND_SERVICE_OK) {
/* Частичный RX не должен выглядеть достоверным после физической ошибки. */
memset(response->rx, 0, sizeof(response->rx));
response->rx_length = 0U;
response->status = status;
return status;
}
/* RX length подтверждается только когда оба transfer завершились успешно. */
response->rx_length = request->rx_length;
response->status = SPI_NOR_COMMAND_SERVICE_OK;
return response->status;
}

View File

@@ -0,0 +1,59 @@
#include "spi_nor_read_service.h"
#include <string.h>
/* Диагностический слой намеренно не содержит program/erase callbacks. */
spi_nor_read_service_status_t spi_nor_read_service_init(
spi_nor_read_service_t *service, void *context,
spi_nor_read_service_read_fn read, uint32_t capacity_bytes)
{
if ((service == NULL) || (read == NULL) || (capacity_bytes == 0U)) {
return SPI_NOR_READ_SERVICE_INVALID;
}
service->context = context;
service->read = read;
service->capacity_bytes = capacity_bytes;
return SPI_NOR_READ_SERVICE_OK;
}
/* Вычитание после проверки address исключает uint32 overflow address+length. */
spi_nor_read_service_status_t spi_nor_read_service_process(
const spi_nor_read_service_t *service,
const spi_nor_read_service_request_t *request,
spi_nor_read_service_response_t *response)
{
spi_nor_read_service_status_t status;
if (response == NULL) {
return SPI_NOR_READ_SERVICE_INVALID;
}
memset(response, 0, sizeof(*response));
if ((service == NULL) || (request == NULL) || (service->read == NULL) ||
(service->capacity_bytes == 0U)) {
response->status = SPI_NOR_READ_SERVICE_UNAVAILABLE;
return response->status;
}
response->sequence = request->sequence;
response->address = request->address;
if ((request->length == 0U) ||
(request->length > SPI_NOR_READ_SERVICE_MAX_BYTES) ||
(request->address >= service->capacity_bytes) ||
((uint32_t)request->length >
(service->capacity_bytes - request->address))) {
response->status = SPI_NOR_READ_SERVICE_INVALID;
return response->status;
}
response->status = SPI_NOR_READ_SERVICE_BUSY;
/* Callback остаётся единственной точкой физического чтения платформы. */
status = service->read(service->context, request->address,
response->data, request->length);
if (status != SPI_NOR_READ_SERVICE_OK) {
/* После SPI-ошибки вызывающий не получает частично заполненный буфер. */
memset(response->data, 0, sizeof(response->data));
response->status = status;
return response->status;
}
response->length = request->length;
response->status = SPI_NOR_READ_SERVICE_OK;
return response->status;
}