Add embedded storage drivers and extend firmware metadata
This commit is contained in:
165
c/spi-nor/Diagnostics/Src/spi_nor_command_service.c
Normal file
165
c/spi-nor/Diagnostics/Src/spi_nor_command_service.c
Normal 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;
|
||||
}
|
||||
59
c/spi-nor/Diagnostics/Src/spi_nor_read_service.c
Normal file
59
c/spi-nor/Diagnostics/Src/spi_nor_read_service.c
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user