Add embedded storage drivers and extend firmware metadata
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
#ifndef SPI_NOR_COMMAND_SERVICE_H
|
||||
#define SPI_NOR_COMMAND_SERVICE_H
|
||||
|
||||
/*
|
||||
* Переносимый read-only сервис коротких сервисных команд SPI NOR.
|
||||
*
|
||||
* Модуль не знает о HAL, Modbus, HTTP и GUI. Он принимает уже распознанный
|
||||
* spi_nor_t, применяет собственный allowlist и выполняет ровно одну законченную
|
||||
* транзакцию. Изменяющие команды намеренно отсутствуют в публичном API.
|
||||
*/
|
||||
|
||||
#include "spi_nor.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Четырёх байт достаточно для opcode и 24-битного адреса READ 0x03. */
|
||||
#define SPI_NOR_COMMAND_SERVICE_MAX_TX_BYTES 4U
|
||||
#define SPI_NOR_COMMAND_SERVICE_MAX_RX_BYTES 64U
|
||||
|
||||
typedef enum
|
||||
{
|
||||
SPI_NOR_COMMAND_SERVICE_IDLE = 0,
|
||||
SPI_NOR_COMMAND_SERVICE_BUSY,
|
||||
SPI_NOR_COMMAND_SERVICE_OK,
|
||||
SPI_NOR_COMMAND_SERVICE_INVALID,
|
||||
SPI_NOR_COMMAND_SERVICE_BLOCKED,
|
||||
SPI_NOR_COMMAND_SERVICE_UNAVAILABLE,
|
||||
SPI_NOR_COMMAND_SERVICE_IO_ERROR,
|
||||
SPI_NOR_COMMAND_SERVICE_TIMEOUT
|
||||
} spi_nor_command_service_status_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint16_t sequence;
|
||||
uint8_t tx_length;
|
||||
uint8_t rx_length;
|
||||
uint8_t tx[SPI_NOR_COMMAND_SERVICE_MAX_TX_BYTES];
|
||||
} spi_nor_command_service_request_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
spi_nor_command_service_status_t status;
|
||||
uint16_t sequence;
|
||||
uint8_t tx_length;
|
||||
uint8_t rx_length;
|
||||
uint8_t tx_echo[SPI_NOR_COMMAND_SERVICE_MAX_TX_BYTES];
|
||||
uint8_t rx[SPI_NOR_COMMAND_SERVICE_MAX_RX_BYTES];
|
||||
} spi_nor_command_service_response_t;
|
||||
|
||||
/*
|
||||
* Allowlist общий для W25Q64/W25Q128 и SST25VF016B:
|
||||
* 0x9F JEDEC ID, 0x05 status register 1, 0x03 обычное чтение данных.
|
||||
*/
|
||||
spi_nor_command_service_status_t spi_nor_command_service_validate(
|
||||
const spi_nor_t *device,
|
||||
const spi_nor_command_service_request_t *request);
|
||||
|
||||
/* Process очищает старый ответ и выполняет не более одной SPI-транзакции. */
|
||||
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);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SPI_NOR_COMMAND_SERVICE_H */
|
||||
@@ -0,0 +1,73 @@
|
||||
#ifndef SPI_NOR_READ_SERVICE_H
|
||||
#define SPI_NOR_READ_SERVICE_H
|
||||
|
||||
/*
|
||||
* Ограниченный read-only сервис диагностики SPI NOR.
|
||||
*
|
||||
* Сервис отделён от HAL, Modbus и приложения. Владелец передаёт только ёмкость
|
||||
* и callback чтения, поэтому portable-ядро SpiNor не получает зависимостей GUI.
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Один маленький пакет ограничивает длительность SPI и Modbus main loop. */
|
||||
#define SPI_NOR_READ_SERVICE_MAX_BYTES 64U
|
||||
|
||||
typedef enum
|
||||
{
|
||||
SPI_NOR_READ_SERVICE_IDLE = 0,
|
||||
SPI_NOR_READ_SERVICE_BUSY,
|
||||
SPI_NOR_READ_SERVICE_OK,
|
||||
SPI_NOR_READ_SERVICE_INVALID,
|
||||
SPI_NOR_READ_SERVICE_UNAVAILABLE,
|
||||
SPI_NOR_READ_SERVICE_IO_ERROR
|
||||
} spi_nor_read_service_status_t;
|
||||
|
||||
/* Callback адаптера не раскрывает драйверу контекст конкретной платформы. */
|
||||
typedef spi_nor_read_service_status_t (*spi_nor_read_service_read_fn)(
|
||||
void *context, uint32_t address, void *data, size_t size);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
void *context;
|
||||
spi_nor_read_service_read_fn read;
|
||||
uint32_t capacity_bytes;
|
||||
} spi_nor_read_service_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint32_t address;
|
||||
uint16_t length;
|
||||
uint16_t sequence;
|
||||
} spi_nor_read_service_request_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
spi_nor_read_service_status_t status;
|
||||
uint32_t address;
|
||||
uint16_t length;
|
||||
uint16_t sequence;
|
||||
uint8_t data[SPI_NOR_READ_SERVICE_MAX_BYTES];
|
||||
} spi_nor_read_service_response_t;
|
||||
|
||||
/* Init проверяет callback и ёмкость, но сам не обращается к SPI. */
|
||||
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);
|
||||
|
||||
/* Process всегда очищает старые данные до проверки нового запроса. */
|
||||
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);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SPI_NOR_READ_SERVICE_H */
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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