From 67cca68fcc69c20abb9b13a32a995c8fa085c2ea Mon Sep 17 00:00:00 2001 From: Andrey Kruchinkin Date: Tue, 15 Sep 2026 12:25:15 +0300 Subject: [PATCH] feat: sync current parallel NAND implementation from templates --- README.md | 24 ++- docs/EMPTY_BOARD_PINOUT.md | 6 +- docs/MEMORY_GAS.md | 42 ++++- include/parallel_nand.h | 13 +- include/parallel_nand_gas.h | 13 +- include/parallel_nand_port.h | 2 + ports/stm32f103rc/README.md | 4 +- .../parallel_nand_port_stm32f103rc.c | 3 +- ports/stm32f103ze/README.md | 3 + .../parallel_nand_port_stm32f103ze.c | 3 +- ports/stm32f407ve/README.md | 5 + .../parallel_nand_port_stm32f407ve.c | 3 +- ports/stm32g474ce/README.md | 3 + .../parallel_nand_port_stm32g474ce.c | 3 +- ports/tms320f2812/README.md | 7 +- .../parallel_nand_port_tms320f2812.c | 3 +- src/parallel_nand.c | 107 +++++++++++- src/parallel_nand_gas.c | 124 +++++++++++++- tests/test_parallel_nand.c | 159 +++++++++++++++++- 19 files changed, 495 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 915931e..d257563 100644 --- a/README.md +++ b/README.md @@ -76,10 +76,28 @@ void memory_init(void) [docs/MEMORY_GAS.md](docs/MEMORY_GAS.md). Электрические соединения — в README соответствующего порта и [docs/EMPTY_BOARD_PINOUT.md](docs/EMPTY_BOARD_PINOUT.md). +## PROGRAM и ERASE + +- `parallel_nand_program_page()` записывает полную main-область и, при + необходимости, полную OOB-область страницы. +- `parallel_nand_erase_block()` стирает один физический блок. +- Перед изменением ядро проверяет заводской bad-block marker, на время операции + снимает `WP#`, ждёт `R/B#`, проверяет fail-бит статуса и снова включает + защиту при любом результате. +- Порт обязан объявить `write_supported = 1`; иначе возвращается + `PNAND_ERROR_WRITE_PROTECTED`. Это защищает платы, где `WP#` постоянно + соединён с GND. +- Через GAS разрушительные команды требуют записи `0xA55A` в `CONFIRM` + непосредственно перед `PROGRAM_PAGE` или `ERASE_BLOCK`. + +NAND можно программировать только из `1` в `0`; для повторной записи сначала +стирается весь блок. Не стирайте заводские bad-блоки и не используйте raw-запись +как файловую систему без ECC, wear leveling и защиты загрузочных блоков. + ## Текущие границы -- Реализовано безопасное чтение, RESET, READ ID, STATUS и bad-block marker. -- PROGRAM/ERASE намеренно отсутствуют; ядро включает аппаратную защиту `WP#`. +- Реализованы чтение, программирование страницы, стирание блока, RESET, + READ ID, STATUS и проверка bad-block marker. - ECC пока не исправляет данные. GUI получает физические main/OOB и должен помечать дамп как raw. - В полном дампе bad-блоки не пропускаются: сохраняется физический порядок. @@ -95,3 +113,5 @@ void memory_init(void) 5. Записать номер страницы в `0xE005/0xE006`, затем `3` в `0xE004`. 6. Дождаться `READY | PAGE_VALID` в `0xE002`. 7. Прочитать main из `0xE100..0xE4FF`, OOB из `0xE500..0xE51F`. +8. PROGRAM/ERASE проверять только на заведомо расходном блоке после сохранения + полного raw-дампа. diff --git a/docs/EMPTY_BOARD_PINOUT.md b/docs/EMPTY_BOARD_PINOUT.md index 5094b1f..e0f72de 100644 --- a/docs/EMPTY_BOARD_PINOUT.md +++ b/docs/EMPTY_BOARD_PINOUT.md @@ -30,7 +30,7 @@ TSOP-48 (суффикс `WP`). На второй фотографии NAND — | `CLE` | 16 | фиксация команды | | `ALE` | 17 | фиксация адреса | | `WE#` | 18 | строб команды/адреса от МК | -| `WP#` | 19 | для read-only 10 кОм на GND; для записи — GPIO и pull-down | +| `WP#` | 19 | GPIO МК и pull-down 10 кОм; только для чтения допустим GND через 10 кОм | | `I/O0` | 29 | двунаправленная шина, бит 0 | | `I/O1` | 30 | двунаправленная шина, бит 1 | | `I/O2` | 31 | двунаправленная шина, бит 2 | @@ -69,10 +69,12 @@ TSOP-48 (суффикс `WP`). На второй фотографии NAND — ## Первый запуск 1. Не устанавливать NAND и проверить 3,3 В, отсутствие КЗ и уровни `CE#/WE#/RE#`. -2. Установить NAND, оставить `WP#` в нуле и выполнить RESET `FFh`. +2. Установить NAND, проверить pull-down и уровень `WP# = 0`, выполнить RESET `FFh`. 3. Прочитать ID `90h`: ожидаемые первые байты для профиля — `2C F1`. 4. Считать одну страницу два раза и сравнить main/OOB побайтно. 5. Только после устойчивого чтения запускать полный дамп через GAS. +6. PROGRAM/ERASE проверять на расходном блоке после сохранения raw-дампа; + убедиться осциллографом, что `WP#` поднимается только на время операции. ## Источники для проверки footprint diff --git a/docs/MEMORY_GAS.md b/docs/MEMORY_GAS.md index b760002..a91be51 100644 --- a/docs/MEMORY_GAS.md +++ b/docs/MEMORY_GAS.md @@ -18,6 +18,8 @@ parallel_nand_gas ▼ общее ядро parallel_nand ├─ TMS320F2812: XINTF + ├─ STM32F103RC: GPIO + ├─ STM32F103ZE: FSMC ├─ STM32F407VE: FSMC └─ STM32G474CE: GPIO ``` @@ -77,7 +79,7 @@ ARM. | Адрес | Доступ | Название | Значение | |---:|:---:|---|---| | `E000` | R | `SIGNATURE` | `0x4E44` (`ND`) | -| `E001` | R | `VERSION` | версия окна, сейчас 1 | +| `E001` | R | `VERSION` | версия окна, сейчас 2 | | `E002` | R | `STATUS` | состояние операции | | `E003` | R | `ERROR` | код `PNAND_*`, 0 = нет ошибки | | `E004` | W | `COMMAND` | команда окна | @@ -92,8 +94,9 @@ ARM. | `E00D` | R | `BAD_BLOCK` | 1 для физически плохого блока | | `E00E` | R | `GENERATION_LO` | счётчик загруженных страниц, low | | `E00F` | R | `GENERATION_HI` | счётчик загруженных страниц, high | -| `E100..E4FF` | R | `PAGE_DATA` | 1024 регистра main, два байта LE | -| `E500..E51F` | R | `PAGE_OOB` | 32 регистра OOB, два байта LE | +| `E010` | W | `CONFIRM` | `0xA55A` непосредственно перед разрушительной командой | +| `E100..E4FF` | R/W | `PAGE_DATA` | 1024 регистра main, два байта LE | +| `E500..E51F` | R/W | `PAGE_OOB` | 32 регистра OOB, два байта LE | Между управляющими регистрами и окнами данных есть зарезервированные адреса. Чтение блока через них не выполняется: каждый запрос должен начинаться внутри @@ -107,6 +110,8 @@ ARM. | 1 | `0x0002` | `BUSY` | | 2 | `0x0004` | `PAGE_VALID` | | 3 | `0x0008` | `BAD_BLOCK` | +| 4 | `0x0010` | `WRITE_SUPPORTED`, порт способен управлять `WP#` | +| 5 | `0x0020` | `BUFFER_DIRTY`, буфер изменён после чтения/очистки | | 15 | `0x8000` | `ERROR`, подробность в `E003` | GUI читает окно данных только при `READY=1`, `PAGE_VALID=1`, `ERROR=0`. @@ -118,6 +123,9 @@ GUI читает окно данных только при `READY=1`, `PAGE_VALI | 1 | `RESET` | сброс NAND и ожидание Ready | | 2 | `READ_ID` | обновляет `E00B/E00C` | | 3 | `READ_PAGE` | загружает main/OOB выбранной страницы | +| 4 | `CLEAR_BUFFER` | заполняет main/OOB значением `0xFF` для подготовки записи | +| 5 | `PROGRAM_PAGE` | программирует выбранную страницу из буфера | +| 6 | `ERASE_BLOCK` | стирает блок, содержащий выбранную страницу | Запись номера страницы и запуск — две отдельные операции. Сначала GUI пишет `PAGE_LO/PAGE_HI`, затем записывает `READ_PAGE` в `COMMAND`. Нельзя отправлять @@ -172,6 +180,28 @@ GUI хранит номер последней полностью записан ## PROGRAM/ERASE -Запись и стирание не включены намеренно. Они требуют отдельного разрешения, -проверки ECC, защиты загрузочных блоков и двухфазного подтверждения через GAS. -До добавления этих функций `WP#` удерживается активным аппаратным портом. +Разрушительные команды доступны только если установлен флаг +`WRITE_SUPPORTED`. Ядро проверяет bad-block marker и отказывается изменять +заводской плохой блок. `WP#` снимается только внутри операции и включается +обратно после проверки status fail-бита, тайм-аута или другой ошибки. + +Для программирования очищенной страницы: + +1. Записать `PAGE_LO/PAGE_HI`. +2. Выполнить `CLEAR_BUFFER`. +3. Записать необходимые main/OOB-регистры. Незаписанные байты останутся + `0xFF`; OOB лучше не менять без определённой ECC-разметки. +4. Записать `0xA55A` в `CONFIRM`. +5. Следующей записью без промежуточных операций отправить `PROGRAM_PAGE`. +6. Проверить `READY`, отсутствие `ERROR` и изменение `GENERATION`. + +Для стирания записать любую страницу целевого блока в `PAGE_LO/PAGE_HI`, затем +`0xA55A` в `CONFIRM` и сразу `ERASE_BLOCK`. Изменение страницы, буфера или +другого управляющего регистра сбрасывает подтверждение. После успешного +стирания `PAGE_VALID` очищается. + +Повторное программирование не может превратить `0` обратно в `1`: для этого +требуется стирание всего блока. Перед первым тестом сохраните полный raw-дамп, +исключите загрузочные блоки и используйте заведомо расходный исправный блок. +Библиотека не реализует ECC, wear leveling, журналирование и восстановление +после пропадания питания. diff --git a/include/parallel_nand.h b/include/parallel_nand.h index 7b6382d..615ab30 100644 --- a/include/parallel_nand.h +++ b/include/parallel_nand.h @@ -35,7 +35,12 @@ typedef enum { PNAND_ERROR_PORT = -2, PNAND_ERROR_TIMEOUT = -3, PNAND_ERROR_RANGE = -4, - PNAND_ERROR_BUFFER = -5 + PNAND_ERROR_BUFFER = -5, + PNAND_ERROR_WRITE_PROTECTED = -6, + PNAND_ERROR_PROGRAM_FAILED = -7, + PNAND_ERROR_ERASE_FAILED = -8, + PNAND_ERROR_BAD_BLOCK = -9, + PNAND_ERROR_CONFIRMATION = -10 } parallel_nand_result_t; typedef struct { @@ -71,6 +76,12 @@ int parallel_nand_read_page(parallel_nand_t *nand, unsigned long page, int parallel_nand_read_page_slice(parallel_nand_t *nand, unsigned long page, unsigned long column, pnand_octet_t *out, unsigned long count); +int parallel_nand_program_page(parallel_nand_t *nand, unsigned long page, + const pnand_octet_t *main_data, + unsigned long main_count, + const pnand_octet_t *oob, + unsigned long oob_count); +int parallel_nand_erase_block(parallel_nand_t *nand, unsigned long block); int parallel_nand_is_bad_block(parallel_nand_t *nand, unsigned long block, int *is_bad); unsigned long parallel_nand_page_count(const parallel_nand_t *nand); diff --git a/include/parallel_nand_gas.h b/include/parallel_nand_gas.h index 50238cc..261f0e9 100644 --- a/include/parallel_nand_gas.h +++ b/include/parallel_nand_gas.h @@ -30,20 +30,28 @@ enum { PNAND_GAS_REG_DEVICE_ID = 0x000C, PNAND_GAS_REG_BAD_BLOCK = 0x000D, PNAND_GAS_REG_GENERATION_LO = 0x000E, - PNAND_GAS_REG_GENERATION_HI = 0x000F + PNAND_GAS_REG_GENERATION_HI = 0x000F, + PNAND_GAS_REG_CONFIRM = 0x0010 }; enum { PNAND_GAS_CMD_NONE = 0, PNAND_GAS_CMD_RESET = 1, PNAND_GAS_CMD_READ_ID = 2, - PNAND_GAS_CMD_READ_PAGE = 3 + PNAND_GAS_CMD_READ_PAGE = 3, + PNAND_GAS_CMD_CLEAR_BUFFER = 4, + PNAND_GAS_CMD_PROGRAM_PAGE = 5, + PNAND_GAS_CMD_ERASE_BLOCK = 6 }; +#define PNAND_GAS_CONFIRM_MAGIC 0xA55AU + #define PNAND_GAS_STATUS_READY 0x0001U #define PNAND_GAS_STATUS_BUSY 0x0002U #define PNAND_GAS_STATUS_PAGE_VALID 0x0004U #define PNAND_GAS_STATUS_BAD_BLOCK 0x0008U +#define PNAND_GAS_STATUS_WRITE_SUPPORTED 0x0010U +#define PNAND_GAS_STATUS_BUFFER_DIRTY 0x0020U #define PNAND_GAS_STATUS_ERROR 0x8000U typedef struct { @@ -56,6 +64,7 @@ typedef struct { unsigned long cached_block; int cached_bad_block; int block_cache_valid; + int destructive_armed; pnand_octet_t id[8]; pnand_octet_t main_data[PNAND_MT29F1G08_PAGE_MAIN]; pnand_octet_t oob[PNAND_MT29F1G08_PAGE_OOB]; diff --git a/include/parallel_nand_port.h b/include/parallel_nand_port.h index a9e73c1..8bf93e3 100644 --- a/include/parallel_nand_port.h +++ b/include/parallel_nand_port.h @@ -19,6 +19,8 @@ typedef struct { int (*is_ready)(void); void (*set_write_protect)(int enabled); void (*delay_us)(unsigned long us); + /* Nonzero only when set_write_protect(0) physically releases WP#. */ + int write_supported; } parallel_nand_port_t; #ifdef __cplusplus diff --git a/ports/stm32f103rc/README.md b/ports/stm32f103rc/README.md index aaba927..21ce9b6 100644 --- a/ports/stm32f103rc/README.md +++ b/ports/stm32f103rc/README.md @@ -47,5 +47,5 @@ int nand_init(void) ``` После успешной инициализации прочитайте четыре байта ID и ожидайте -`AD DC 80 95`. Ядро библиотеки работает только на чтение и удерживает `WP#` -в активном низком уровне; PROGRAM/ERASE пока намеренно не реализованы. +`AD DC 80 95`. Порт поддерживает PROGRAM/ERASE: ядро держит `WP#` в активном +низком уровне между операциями и поднимает его только на время изменения NAND. diff --git a/ports/stm32f103rc/parallel_nand_port_stm32f103rc.c b/ports/stm32f103rc/parallel_nand_port_stm32f103rc.c index 1a4d8f4..cfa5f39 100644 --- a/ports/stm32f103rc/parallel_nand_port_stm32f103rc.c +++ b/ports/stm32f103rc/parallel_nand_port_stm32f103rc.c @@ -133,7 +133,8 @@ static void f103rc_delay_us(unsigned long us) static const parallel_nand_port_t f103rc_port = { f103rc_init, f103rc_select, f103rc_deselect, f103rc_write_command, f103rc_write_address, f103rc_write_data, - f103rc_read_data, f103rc_is_ready, f103rc_set_wp, f103rc_delay_us + f103rc_read_data, f103rc_is_ready, f103rc_set_wp, f103rc_delay_us, + 1 }; const parallel_nand_port_t *parallel_nand_port_stm32f103rc(void) diff --git a/ports/stm32f103ze/README.md b/ports/stm32f103ze/README.md index 0be0c35..115e7cd 100644 --- a/ports/stm32f103ze/README.md +++ b/ports/stm32f103ze/README.md @@ -57,3 +57,6 @@ int nand_init(void) Начальные тайминги намеренно консервативны. После запуска проверьте осциллографом длительности `WE#`/`RE#` и время установки данных. Ожидаемый ID HY27UF084G2M: `AD DC 80 95`. + +Порт поддерживает PROGRAM/ERASE. `WP#` на `PG8` остаётся активным между +операциями и снимается ядром только на время программирования или стирания. diff --git a/ports/stm32f103ze/parallel_nand_port_stm32f103ze.c b/ports/stm32f103ze/parallel_nand_port_stm32f103ze.c index e1e6a3e..83134ef 100644 --- a/ports/stm32f103ze/parallel_nand_port_stm32f103ze.c +++ b/ports/stm32f103ze/parallel_nand_port_stm32f103ze.c @@ -174,7 +174,8 @@ static const parallel_nand_port_t f103ze_port = { f103ze_read_data, f103ze_is_ready, f103ze_set_write_protect, - f103ze_delay_us + f103ze_delay_us, + 1 }; const parallel_nand_port_t *parallel_nand_port_stm32f103ze(void) diff --git a/ports/stm32f407ve/README.md b/ports/stm32f407ve/README.md index 0decb57..0ec2c78 100644 --- a/ports/stm32f407ve/README.md +++ b/ports/stm32f407ve/README.md @@ -24,3 +24,8 @@ `stm32f4xx_ll_fsmc.c`, включить FSMC и не создавать второй `NAND_HandleTypeDef` для Bank 2. Начальные тайминги консервативны; финальные значения подтвердить осциллографом. + +В базовой схеме `WP#` аппаратно зафиксирован в активном уровне, поэтому порт +объявляет `write_supported = 0`, а PROGRAM/ERASE возвращают +`PNAND_ERROR_WRITE_PROTECTED`. Для записи назначьте отдельный GPIO с внешним +pull-down, реализуйте управление `WP#` и включите признак поддержки порта. diff --git a/ports/stm32f407ve/parallel_nand_port_stm32f407ve.c b/ports/stm32f407ve/parallel_nand_port_stm32f407ve.c index 50290c2..d2fab45 100644 --- a/ports/stm32f407ve/parallel_nand_port_stm32f407ve.c +++ b/ports/stm32f407ve/parallel_nand_port_stm32f407ve.c @@ -84,7 +84,8 @@ static void f407_delay_us(unsigned long us) static const parallel_nand_port_t f407_port = { f407_init, f407_select, f407_deselect, f407_write_command, f407_write_address, f407_write_data, - f407_read_data, f407_is_ready, f407_set_wp, f407_delay_us + f407_read_data, f407_is_ready, f407_set_wp, f407_delay_us, + 0 }; const parallel_nand_port_t *parallel_nand_port_stm32f407ve(void) diff --git a/ports/stm32g474ce/README.md b/ports/stm32g474ce/README.md index 24e07e6..8a938ad 100644 --- a/ports/stm32g474ce/README.md +++ b/ports/stm32g474ce/README.md @@ -19,3 +19,6 @@ HAL только для инициализации, а циклы NAND выпо Выбранные `PB3/PB4` нельзя одновременно использовать для trace/JTAG; SWD на `PA13/PA14` остаётся доступен. Если эти пины нужны проекту, измените таблицу и операции шины: текущая оптимизация рассчитана на непрерывные `PB0..PB7`. + +Порт поддерживает PROGRAM/ERASE через управляемый `WP#` на `PA5`. Внешний +pull-down сохраняет защиту до инициализации GPIO и при сбросе МК. diff --git a/ports/stm32g474ce/parallel_nand_port_stm32g474ce.c b/ports/stm32g474ce/parallel_nand_port_stm32g474ce.c index 3f18b64..b9819b6 100644 --- a/ports/stm32g474ce/parallel_nand_port_stm32g474ce.c +++ b/ports/stm32g474ce/parallel_nand_port_stm32g474ce.c @@ -125,7 +125,8 @@ static void g474_delay_us(unsigned long us) static const parallel_nand_port_t g474_port = { g474_init, g474_select, g474_deselect, g474_write_command, g474_write_address, g474_write_data, - g474_read_data, g474_is_ready, g474_set_wp, g474_delay_us + g474_read_data, g474_is_ready, g474_set_wp, g474_delay_us, + 1 }; const parallel_nand_port_t *parallel_nand_port_stm32g474ce(void) diff --git a/ports/tms320f2812/README.md b/ports/tms320f2812/README.md index 7a33632..db1dfd1 100644 --- a/ports/tms320f2812/README.md +++ b/ports/tms320f2812/README.md @@ -13,9 +13,10 @@ Адреса C28x задаются в 16-битных словах: data `0x100000`, command `0x100001`, address `0x100002`. Используется только младший байт XINTF. -`WP#` в базовом варианте аппаратный: для режима только чтения соединить с GND -через 10 кОм; если позже понадобится PROGRAM/ERASE, заменить на GPIO и реализовать -`tms_set_wp()`. +`WP#` в базовом варианте аппаратный и соединён с GND через 10 кОм. Поэтому +порт объявляет `write_supported = 0`, а PROGRAM/ERASE возвращают +`PNAND_ERROR_WRITE_PROTECTED`. Для записи нужно перенести `WP#` на GPIO, +реализовать `tms_set_wp()` и установить признак поддержки в структуре порта. Задержка в порте приблизительная. После выбора частоты CPU проверить импульсы осциллографом и при необходимости изменить `PNAND_TMS_CPU_MHZ`. diff --git a/ports/tms320f2812/parallel_nand_port_tms320f2812.c b/ports/tms320f2812/parallel_nand_port_tms320f2812.c index b1dda6d..d82f325 100644 --- a/ports/tms320f2812/parallel_nand_port_tms320f2812.c +++ b/ports/tms320f2812/parallel_nand_port_tms320f2812.c @@ -56,7 +56,8 @@ static void tms_set_wp(int enabled) static const parallel_nand_port_t tms_port = { tms_init, tms_select, tms_deselect, tms_write_command, tms_write_address, tms_write_data, - tms_read_data, tms_is_ready, tms_set_wp, tms_delay_us + tms_read_data, tms_is_ready, tms_set_wp, tms_delay_us, + 0 }; const parallel_nand_port_t *parallel_nand_port_tms320f2812(void) diff --git a/src/parallel_nand.c b/src/parallel_nand.c index bd182a9..90d28b1 100644 --- a/src/parallel_nand.c +++ b/src/parallel_nand.c @@ -4,8 +4,15 @@ #define PNAND_CMD_READ_2 0x30U #define PNAND_CMD_READ_ID 0x90U #define PNAND_CMD_READ_STATUS 0x70U +#define PNAND_CMD_PROGRAM_1 0x80U +#define PNAND_CMD_PROGRAM_2 0x10U +#define PNAND_CMD_ERASE_1 0x60U +#define PNAND_CMD_ERASE_2 0xD0U #define PNAND_CMD_RESET 0xFFU +#define PNAND_STATUS_FAIL 0x01U +#define PNAND_STATUS_WP 0x80U + const parallel_nand_geometry_t parallel_nand_mt29f1g08_geometry = { PNAND_MT29F1G08_PAGE_MAIN, PNAND_MT29F1G08_PAGE_OOB, @@ -67,6 +74,8 @@ int parallel_nand_reset(parallel_nand_t *nand) nand->port->select(); nand->port->write_command(PNAND_CMD_RESET); nand->port->deselect(); + /* R/B# may not fall until tWB after the command cycle. */ + nand->port->delay_us(1UL); return wait_ready(nand); } @@ -80,7 +89,7 @@ int parallel_nand_init(parallel_nand_t *nand, nand->geometry = *geometry; if (port->init() != 0) return PNAND_ERROR_PORT; port->deselect(); - /* Ядро пока только читает: аппаратно запрещаем PROGRAM/ERASE. */ + /* WP# stays asserted except for the shortest possible mutation window. */ port->set_write_protect(1); return parallel_nand_reset(nand); } @@ -132,6 +141,7 @@ static int begin_page_read(parallel_nand_t *nand, unsigned long page, } nand->port->write_command(PNAND_CMD_READ_2); nand->port->deselect(); + nand->port->delay_us(1UL); /* Cover tWB before polling R/B#. */ return wait_ready(nand); } @@ -177,6 +187,101 @@ int parallel_nand_read_page(parallel_nand_t *nand, unsigned long page, return PNAND_OK; } +static void write_address_cycles(parallel_nand_t *nand, + unsigned long value, + unsigned int count) +{ + unsigned int i; + for (i = 0U; i < count; i++) { + nand->port->write_address((pnand_octet_t)(value & 0xFFUL)); + value >>= 8; + } +} + +static int mutation_supported(const parallel_nand_t *nand) +{ + return nand != 0 && port_valid(nand->port) && + nand->port->write_data != 0 && + nand->port->write_supported != 0; +} + +static int finish_mutation(parallel_nand_t *nand, int failure_result) +{ + pnand_octet_t status = 0U; + int result = wait_ready(nand); + if (result == PNAND_OK) + result = parallel_nand_read_status(nand, &status); + nand->port->set_write_protect(1); + if (result != PNAND_OK) return result; + if ((status & PNAND_STATUS_WP) == 0U) + return PNAND_ERROR_WRITE_PROTECTED; + return (status & PNAND_STATUS_FAIL) != 0U ? failure_result : PNAND_OK; +} + +int parallel_nand_program_page(parallel_nand_t *nand, unsigned long page, + const pnand_octet_t *main_data, + unsigned long main_count, + const pnand_octet_t *oob, + unsigned long oob_count) +{ + unsigned long i; + unsigned long block; + int bad = 0; + int result; + if (nand == 0 || main_data == 0) return PNAND_ERROR_ARGUMENT; + if (!mutation_supported(nand)) return PNAND_ERROR_WRITE_PROTECTED; + if (page >= parallel_nand_page_count(nand)) return PNAND_ERROR_RANGE; + if (main_count != nand->geometry.page_main_size) + return PNAND_ERROR_BUFFER; + if ((oob == 0 && oob_count != 0UL) || + (oob != 0 && oob_count != nand->geometry.page_oob_size)) + return PNAND_ERROR_BUFFER; + + block = page / nand->geometry.pages_per_block; + result = parallel_nand_is_bad_block(nand, block, &bad); + if (result != PNAND_OK) return result; + if (bad) return PNAND_ERROR_BAD_BLOCK; + + nand->port->set_write_protect(0); + nand->port->delay_us(1UL); + nand->port->select(); + nand->port->write_command(PNAND_CMD_PROGRAM_1); + write_address_cycles(nand, 0UL, nand->geometry.column_cycles); + write_address_cycles(nand, page, nand->geometry.row_cycles); + for (i = 0UL; i < main_count; i++) + nand->port->write_data(main_data[i] & 0xFFU); + for (i = 0UL; i < oob_count; i++) + nand->port->write_data(oob[i] & 0xFFU); + nand->port->write_command(PNAND_CMD_PROGRAM_2); + nand->port->deselect(); + nand->port->delay_us(1UL); /* Cover tWB before polling R/B#. */ + return finish_mutation(nand, PNAND_ERROR_PROGRAM_FAILED); +} + +int parallel_nand_erase_block(parallel_nand_t *nand, unsigned long block) +{ + unsigned long first_page; + int bad = 0; + int result; + if (nand == 0) return PNAND_ERROR_ARGUMENT; + if (!mutation_supported(nand)) return PNAND_ERROR_WRITE_PROTECTED; + if (block >= nand->geometry.block_count) return PNAND_ERROR_RANGE; + result = parallel_nand_is_bad_block(nand, block, &bad); + if (result != PNAND_OK) return result; + if (bad) return PNAND_ERROR_BAD_BLOCK; + + first_page = block * nand->geometry.pages_per_block; + nand->port->set_write_protect(0); + nand->port->delay_us(1UL); + nand->port->select(); + nand->port->write_command(PNAND_CMD_ERASE_1); + write_address_cycles(nand, first_page, nand->geometry.row_cycles); + nand->port->write_command(PNAND_CMD_ERASE_2); + nand->port->deselect(); + nand->port->delay_us(1UL); /* Cover tWB before polling R/B#. */ + return finish_mutation(nand, PNAND_ERROR_ERASE_FAILED); +} + int parallel_nand_is_bad_block(parallel_nand_t *nand, unsigned long block, int *is_bad) { diff --git a/src/parallel_nand_gas.c b/src/parallel_nand_gas.c index 461374b..996d24d 100644 --- a/src/parallel_nand_gas.c +++ b/src/parallel_nand_gas.c @@ -6,6 +6,20 @@ static unsigned int pack_pair(const pnand_octet_t *data, unsigned int index) ((data[index * 2U + 1U] & 0xFFU) << 8)); } +static void unpack_pair(pnand_octet_t *data, unsigned int index, + unsigned int value) +{ + data[index * 2U] = (pnand_octet_t)(value & 0xFFU); + data[index * 2U + 1U] = (pnand_octet_t)((value >> 8) & 0xFFU); +} + +static unsigned int persistent_status(const parallel_nand_gas_t *gas) +{ + return gas->nand->port->write_supported != 0 + ? PNAND_GAS_STATUS_WRITE_SUPPORTED + : 0U; +} + parallel_nand_gas_result_t parallel_nand_gas_read( parallel_nand_gas_t *gas, unsigned int address, unsigned int *value) { @@ -13,11 +27,16 @@ parallel_nand_gas_result_t parallel_nand_gas_read( const parallel_nand_geometry_t *g; if (gas == 0 || value == 0 || address < gas->base) return PNAND_GAS_REJECTED; + /* Confirmation authorizes exactly the immediately following write. */ + gas->destructive_armed = 0; g = &gas->nand->geometry; offset = address - gas->base; if (offset >= PNAND_GAS_REGION_REGS) return PNAND_GAS_NO_REG; if (offset >= PNAND_GAS_DATA_OFFSET && offset < PNAND_GAS_DATA_OFFSET + PNAND_GAS_DATA_REGS) { + if ((unsigned long)(offset - PNAND_GAS_DATA_OFFSET) * 2UL >= + g->page_main_size) + return PNAND_GAS_NO_REG; if ((gas->status & PNAND_GAS_STATUS_PAGE_VALID) == 0U) return PNAND_GAS_REJECTED; *value = pack_pair(gas->main_data, @@ -26,6 +45,9 @@ parallel_nand_gas_result_t parallel_nand_gas_read( } if (offset >= PNAND_GAS_OOB_OFFSET && offset < PNAND_GAS_OOB_OFFSET + PNAND_GAS_OOB_REGS) { + if ((unsigned long)(offset - PNAND_GAS_OOB_OFFSET) * 2UL >= + g->page_oob_size) + return PNAND_GAS_NO_REG; if ((gas->status & PNAND_GAS_STATUS_PAGE_VALID) == 0U) return PNAND_GAS_REJECTED; *value = pack_pair(gas->oob, @@ -34,7 +56,7 @@ parallel_nand_gas_result_t parallel_nand_gas_read( } switch (offset) { case PNAND_GAS_REG_SIGNATURE: *value = 0x4E44U; break; /* ND */ - case PNAND_GAS_REG_VERSION: *value = 0x0001U; break; + case PNAND_GAS_REG_VERSION: *value = 0x0002U; break; case PNAND_GAS_REG_STATUS: *value = gas->status; break; case PNAND_GAS_REG_ERROR: *value = (unsigned int)gas->error & 0xFFFFU; break; case PNAND_GAS_REG_COMMAND: *value = PNAND_GAS_CMD_NONE; break; @@ -50,6 +72,7 @@ parallel_nand_gas_result_t parallel_nand_gas_read( *value = (gas->status & PNAND_GAS_STATUS_BAD_BLOCK) != 0U; break; case PNAND_GAS_REG_GENERATION_LO: *value = (unsigned int)gas->generation & 0xFFFFU; break; case PNAND_GAS_REG_GENERATION_HI: *value = (unsigned int)(gas->generation >> 16) & 0xFFFFU; break; + case PNAND_GAS_REG_CONFIRM: *value = 0U; break; default: return PNAND_GAS_NO_REG; } *value &= 0xFFFFU; @@ -60,7 +83,7 @@ static void finish(parallel_nand_gas_t *gas, int result) { gas->error = result; gas->status &= (0xFFFFU ^ PNAND_GAS_STATUS_BUSY); - gas->status |= PNAND_GAS_STATUS_READY; + gas->status |= PNAND_GAS_STATUS_READY | persistent_status(gas); if (result != PNAND_OK) gas->status |= PNAND_GAS_STATUS_ERROR; } @@ -69,7 +92,11 @@ static parallel_nand_gas_result_t run_command(parallel_nand_gas_t *gas, { int result = PNAND_OK; int bad = 0; - gas->status = PNAND_GAS_STATUS_BUSY; + int armed = gas->destructive_armed; + unsigned int i; + unsigned int previous_status = gas->status; + gas->destructive_armed = 0; + gas->status = PNAND_GAS_STATUS_BUSY | persistent_status(gas); if (command == PNAND_GAS_CMD_RESET) { result = parallel_nand_reset(gas->nand); } else if (command == PNAND_GAS_CMD_READ_ID) { @@ -101,8 +128,59 @@ static parallel_nand_gas_result_t run_command(parallel_nand_gas_t *gas, if (result == PNAND_OK && bad) gas->status |= PNAND_GAS_STATUS_BAD_BLOCK; } + } else if (command == PNAND_GAS_CMD_CLEAR_BUFFER) { + for (i = 0U; i < PNAND_MT29F1G08_PAGE_MAIN; i++) + gas->main_data[i] = 0xFFU; + for (i = 0U; i < PNAND_MT29F1G08_PAGE_OOB; i++) + gas->oob[i] = 0xFFU; + gas->status |= PNAND_GAS_STATUS_PAGE_VALID | + PNAND_GAS_STATUS_BUFFER_DIRTY; + gas->generation++; + } else if (command == PNAND_GAS_CMD_PROGRAM_PAGE) { + if (!armed || + (previous_status & PNAND_GAS_STATUS_PAGE_VALID) == 0U) { + gas->status |= previous_status & + (PNAND_GAS_STATUS_PAGE_VALID | + PNAND_GAS_STATUS_BUFFER_DIRTY | + PNAND_GAS_STATUS_BAD_BLOCK); + finish(gas, PNAND_ERROR_CONFIRMATION); + return PNAND_GAS_REJECTED; + } + result = parallel_nand_program_page( + gas->nand, gas->selected_page, + gas->main_data, gas->nand->geometry.page_main_size, + gas->oob, gas->nand->geometry.page_oob_size); + gas->status |= PNAND_GAS_STATUS_PAGE_VALID; + if (result == PNAND_OK) { + gas->generation++; + gas->block_cache_valid = 0; + } else { + gas->status |= PNAND_GAS_STATUS_BUFFER_DIRTY; + } + } else if (command == PNAND_GAS_CMD_ERASE_BLOCK) { + unsigned long block; + if (!armed || + gas->selected_page >= parallel_nand_page_count(gas->nand)) { + finish(gas, !armed ? PNAND_ERROR_CONFIRMATION : + PNAND_ERROR_RANGE); + return PNAND_GAS_REJECTED; + } + block = gas->selected_page / gas->nand->geometry.pages_per_block; + result = parallel_nand_erase_block(gas->nand, block); + if (result == PNAND_OK) { + gas->status &= (0xFFFFU ^ (PNAND_GAS_STATUS_PAGE_VALID | + PNAND_GAS_STATUS_BUFFER_DIRTY | + PNAND_GAS_STATUS_BAD_BLOCK)); + gas->generation++; + gas->block_cache_valid = 0; + } else { + gas->status |= previous_status & + (PNAND_GAS_STATUS_PAGE_VALID | + PNAND_GAS_STATUS_BUFFER_DIRTY | + PNAND_GAS_STATUS_BAD_BLOCK); + } } else { - gas->status = PNAND_GAS_STATUS_READY; + gas->status = PNAND_GAS_STATUS_READY | persistent_status(gas); return PNAND_GAS_REJECTED; } finish(gas, result); @@ -119,18 +197,47 @@ parallel_nand_gas_result_t parallel_nand_gas_write( if ((gas->status & PNAND_GAS_STATUS_BUSY) != 0U) return PNAND_GAS_REJECTED; value &= 0xFFFFU; + if (offset >= PNAND_GAS_DATA_OFFSET && + offset < PNAND_GAS_DATA_OFFSET + PNAND_GAS_DATA_REGS) { + unsigned int index = offset - PNAND_GAS_DATA_OFFSET; + if ((gas->status & PNAND_GAS_STATUS_PAGE_VALID) == 0U || + (unsigned long)index * 2UL >= gas->nand->geometry.page_main_size) + return PNAND_GAS_REJECTED; + gas->destructive_armed = 0; + unpack_pair(gas->main_data, index, value); + gas->status |= PNAND_GAS_STATUS_BUFFER_DIRTY; + return PNAND_GAS_OK; + } + if (offset >= PNAND_GAS_OOB_OFFSET && + offset < PNAND_GAS_OOB_OFFSET + PNAND_GAS_OOB_REGS) { + unsigned int index = offset - PNAND_GAS_OOB_OFFSET; + if ((gas->status & PNAND_GAS_STATUS_PAGE_VALID) == 0U || + (unsigned long)index * 2UL >= gas->nand->geometry.page_oob_size) + return PNAND_GAS_REJECTED; + gas->destructive_armed = 0; + unpack_pair(gas->oob, index, value); + gas->status |= PNAND_GAS_STATUS_BUFFER_DIRTY; + return PNAND_GAS_OK; + } switch (offset) { case PNAND_GAS_REG_COMMAND: return run_command(gas, value); + case PNAND_GAS_REG_CONFIRM: + gas->destructive_armed = value == PNAND_GAS_CONFIRM_MAGIC; + return gas->destructive_armed ? PNAND_GAS_OK : PNAND_GAS_REJECTED; case PNAND_GAS_REG_PAGE_LO: + gas->destructive_armed = 0; gas->selected_page = (gas->selected_page & 0xFFFF0000UL) | value; gas->status &= (0xFFFFU ^ PNAND_GAS_STATUS_PAGE_VALID); return PNAND_GAS_OK; case PNAND_GAS_REG_PAGE_HI: + gas->destructive_armed = 0; gas->selected_page = (gas->selected_page & 0x0000FFFFUL) | ((unsigned long)value << 16); gas->status &= (0xFFFFU ^ PNAND_GAS_STATUS_PAGE_VALID); return PNAND_GAS_OK; - default: return PNAND_GAS_READ_ONLY; + default: + gas->destructive_armed = 0; + return PNAND_GAS_READ_ONLY; } } @@ -140,12 +247,17 @@ void parallel_nand_gas_init(parallel_nand_gas_t *gas, unsigned int i; gas->nand = nand; gas->base = base; - gas->status = PNAND_GAS_STATUS_READY; + gas->status = PNAND_GAS_STATUS_READY | persistent_status(gas); gas->error = PNAND_OK; gas->selected_page = 0UL; gas->generation = 0UL; gas->cached_block = 0UL; gas->cached_bad_block = 0; gas->block_cache_valid = 0; + gas->destructive_armed = 0; for (i = 0U; i < 8U; i++) gas->id[i] = 0U; + for (i = 0U; i < PNAND_MT29F1G08_PAGE_MAIN; i++) + gas->main_data[i] = 0xFFU; + for (i = 0U; i < PNAND_MT29F1G08_PAGE_OOB; i++) + gas->oob[i] = 0xFFU; } diff --git a/tests/test_parallel_nand.c b/tests/test_parallel_nand.c index d2ae00a..82dc797 100644 --- a/tests/test_parallel_nand.c +++ b/tests/test_parallel_nand.c @@ -5,7 +5,7 @@ #include "parallel_nand_gas.h" typedef struct { char kind; unsigned int value; } event_t; -static event_t events[64]; +static event_t events[256]; static unsigned int event_count; static pnand_octet_t input_data[32]; static unsigned int input_count; @@ -33,17 +33,35 @@ static void fake_delay(unsigned long us) { (void)us; } static const parallel_nand_port_t fake_port = { fake_init, fake_select, fake_deselect, fake_cmd, fake_addr, fake_write, - fake_read, fake_ready, fake_wp, fake_delay + fake_read, fake_ready, fake_wp, fake_delay, 1 }; static void clear_events(void) { event_count = 0U; } +static unsigned int find_event(unsigned int start, char kind, + unsigned int value) +{ + unsigned int i; + for (i = start; i < event_count; i++) { + if (events[i].kind == kind && events[i].value == value) return i; + } + return event_count; +} + int main(void) { parallel_nand_t nand; pnand_octet_t id[2]; pnand_octet_t bytes[2]; + pnand_octet_t main_data[2] = {0xAAU, 0x55U}; + pnand_octet_t oob[2] = {0xFFU, 0xFEU}; parallel_nand_gas_t gas; + parallel_nand_geometry_t small_geometry = { + 2UL, 2UL, 2UL, 4UL, 2U, 2U, 100UL + }; + parallel_nand_port_t read_only_port; + parallel_nand_t read_only_nand; + unsigned int pos; unsigned int reg; assert(parallel_nand_init(&nand, &fake_port, @@ -96,9 +114,96 @@ int main(void) assert(events[6].kind == 'A' && events[6].value == 0x02U); assert(events[7].kind == 'C' && events[7].value == 0x30U); + assert(parallel_nand_init(&nand, &fake_port, &small_geometry) == + PNAND_OK); + clear_events(); + input_data[0] = 0xFFU; + input_data[1] = 0xFFU; + input_data[2] = 0x80U; + input_count = 3U; + input_pos = 0U; + assert(parallel_nand_program_page(&nand, 3UL, main_data, 2UL, + oob, 2UL) == PNAND_OK); + pos = find_event(0U, 'P', 0U); + assert(pos < event_count); + pos = find_event(pos + 1U, 'C', 0x80U); + assert(pos < event_count); + pos = find_event(pos + 1U, 'W', 0xAAU); + assert(pos < event_count); + pos = find_event(pos + 1U, 'W', 0x55U); + assert(pos < event_count); + pos = find_event(pos + 1U, 'W', 0xFFU); + assert(pos < event_count); + pos = find_event(pos + 1U, 'W', 0xFEU); + assert(pos < event_count); + pos = find_event(pos + 1U, 'C', 0x10U); + assert(pos < event_count); + pos = find_event(pos + 1U, 'C', 0x70U); + assert(pos < event_count); + assert(events[event_count - 1U].kind == 'P' && + events[event_count - 1U].value == 1U); + + clear_events(); + input_data[0] = 0xFFU; + input_data[1] = 0xFFU; + input_data[2] = 0x80U; + input_count = 3U; + input_pos = 0U; + assert(parallel_nand_erase_block(&nand, 2UL) == PNAND_OK); + pos = find_event(0U, 'P', 0U); + pos = find_event(pos + 1U, 'C', 0x60U); + assert(pos < event_count); + pos = find_event(pos + 1U, 'A', 0x04U); + assert(pos < event_count); + pos = find_event(pos + 1U, 'C', 0xD0U); + assert(pos < event_count); + assert(events[event_count - 1U].kind == 'P' && + events[event_count - 1U].value == 1U); + + clear_events(); + input_data[0] = 0xFFU; + input_data[1] = 0xFFU; + input_data[2] = 0x81U; + input_count = 3U; + input_pos = 0U; + assert(parallel_nand_program_page(&nand, 0UL, main_data, 2UL, + 0, 0UL) == + PNAND_ERROR_PROGRAM_FAILED); + assert(events[event_count - 1U].kind == 'P' && + events[event_count - 1U].value == 1U); + + clear_events(); + input_data[0] = 0xFFU; + input_data[1] = 0xFFU; + input_data[2] = 0x00U; + input_count = 3U; + input_pos = 0U; + assert(parallel_nand_program_page(&nand, 0UL, main_data, 2UL, + 0, 0UL) == + PNAND_ERROR_WRITE_PROTECTED); + assert(events[event_count - 1U].kind == 'P' && + events[event_count - 1U].value == 1U); + + clear_events(); + input_data[0] = 0x00U; + input_count = 1U; + input_pos = 0U; + assert(parallel_nand_erase_block(&nand, 0UL) == + PNAND_ERROR_BAD_BLOCK); + assert(find_event(0U, 'P', 0U) == event_count); + + read_only_port = fake_port; + read_only_port.write_supported = 0; + assert(parallel_nand_init(&read_only_nand, &read_only_port, + &small_geometry) == PNAND_OK); + assert(parallel_nand_erase_block(&read_only_nand, 0UL) == + PNAND_ERROR_WRITE_PROTECTED); + parallel_nand_gas_init(&gas, &nand, PNAND_GAS_DEFAULT_BASE); assert(parallel_nand_gas_read(&gas, 0xE000U, ®) == PNAND_GAS_OK); assert(reg == 0x4E44U); + assert(parallel_nand_gas_read(&gas, 0xE001U, ®) == PNAND_GAS_OK); + assert(reg == 0x0002U); assert(parallel_nand_gas_write(&gas, 0xE005U, 0x1234U) == PNAND_GAS_OK); assert(parallel_nand_gas_write(&gas, 0xE006U, 0x0001U) == @@ -112,6 +217,56 @@ int main(void) assert(parallel_nand_gas_write(&gas, 0xE000U, 0U) == PNAND_GAS_READ_ONLY); + assert((gas.status & PNAND_GAS_STATUS_WRITE_SUPPORTED) != 0U); + assert(parallel_nand_gas_write(&gas, 0xE005U, 0U) == PNAND_GAS_OK); + assert(parallel_nand_gas_write(&gas, 0xE006U, 0U) == PNAND_GAS_OK); + assert(parallel_nand_gas_write(&gas, 0xE004U, + PNAND_GAS_CMD_CLEAR_BUFFER) == + PNAND_GAS_OK); + assert(parallel_nand_gas_write(&gas, 0xE100U, 0x1234U) == + PNAND_GAS_OK); + assert(gas.main_data[0] == 0x34U && gas.main_data[1] == 0x12U); + assert(parallel_nand_gas_write(&gas, 0xE004U, + PNAND_GAS_CMD_PROGRAM_PAGE) == + PNAND_GAS_REJECTED); + assert(gas.error == PNAND_ERROR_CONFIRMATION); + + assert(parallel_nand_gas_write(&gas, 0xE010U, + PNAND_GAS_CONFIRM_MAGIC) == + PNAND_GAS_OK); + assert(parallel_nand_gas_read(&gas, 0xE002U, ®) == PNAND_GAS_OK); + assert(parallel_nand_gas_write(&gas, 0xE004U, + PNAND_GAS_CMD_PROGRAM_PAGE) == + PNAND_GAS_REJECTED); + assert(gas.error == PNAND_ERROR_CONFIRMATION); + + input_data[0] = 0xFFU; + input_data[1] = 0xFFU; + input_data[2] = 0x80U; + input_count = 3U; + input_pos = 0U; + assert(parallel_nand_gas_write(&gas, 0xE010U, + PNAND_GAS_CONFIRM_MAGIC) == + PNAND_GAS_OK); + assert(parallel_nand_gas_write(&gas, 0xE004U, + PNAND_GAS_CMD_PROGRAM_PAGE) == + PNAND_GAS_OK); + assert((gas.status & PNAND_GAS_STATUS_BUFFER_DIRTY) == 0U); + + assert(parallel_nand_gas_write(&gas, 0xE005U, 2U) == PNAND_GAS_OK); + input_data[0] = 0xFFU; + input_data[1] = 0xFFU; + input_data[2] = 0x80U; + input_count = 3U; + input_pos = 0U; + assert(parallel_nand_gas_write(&gas, 0xE010U, + PNAND_GAS_CONFIRM_MAGIC) == + PNAND_GAS_OK); + assert(parallel_nand_gas_write(&gas, 0xE004U, + PNAND_GAS_CMD_ERASE_BLOCK) == + PNAND_GAS_OK); + assert((gas.status & PNAND_GAS_STATUS_PAGE_VALID) == 0U); + puts("parallel_nand: OK"); return 0; }