Формат данных EEPROM
Контроллеры JetHome хранят идентификационные данные плат в микросхеме EEPROM на шине I2C: название и версию платы, серийный номер, MAC-адрес, идентификаторы процессора, а на части устройств — цифровую подпись производителя. Адрес микросхемы указан в описании каждого контроллера.
Данные записаны в формате JEEFS: заголовок платы с контрольной суммой, за ним — простая файловая система. Формат открыт, его эталонная реализация — библиотека jeefs для C, C++, Python и Rust.
See also
Проверка подписи производителя описана в разделе Проверка цифровой подписи.
Организация памяти
Смещение |
Размер |
Содержимое |
|---|---|---|
|
256 байт |
Заголовок платы (у устаревшей версии 1 — 512 байт) |
|
переменный |
Файловая система: цепочка файлов, каждый файл — заголовок 28 байт и данные |
после последнего файла |
до конца памяти |
Свободное место, байты |
Общие правила для всех структур:
Многобайтовые поля записаны в порядке little-endian.
Структуры упакованы без выравнивания.
Контрольная сумма — CRC32 по стандарту IEEE 802.3 (полином
0xEDB88320), совпадает с функциейcrc32()библиотеки zlib. Хранится в порядке little-endian. В заголовке платы и в записиdevice.idона покрывает все байты структуры до поля контрольной суммы. У файла контрольных сумм две:headerCrc32покрывает байты заголовка файла до себя, аcrc32— данные файла, а не заголовок.Резервные байты внутри записанных структур равны
0x00. Незаписанная область стертой микросхемы читается как0xFF. Оба значения означают отсутствие данных, а целостность записанных данных определяют сигнатура, версия и контрольная сумма.Строковые поля бывают двух видов:
строки с завершающим нулем (
boardname,boardversion) — до 31 символа, после строки обязателен нулевой байт;строки фиксированной длины (
board_serial,usid,cpuid) — печатаемые символы ASCII (0x20–0x7E), дополненные нулевыми байтами. Если значение занимает поле целиком, нулевого байта нет.
Заголовок платы
Заголовок описывает одну плату. Процессорный модуль и материнская плата несут каждый свой заголовок в своей микросхеме EEPROM.
Определение версии заголовка
Первые 12 байт одинаковы для всех версий:
Смещение |
Размер |
Поле |
Описание |
|---|---|---|---|
0–7 |
8 |
|
Сигнатура |
8 |
1 |
|
Версия заголовка |
9–11 |
3 |
— |
Назначение зависит от версии |
Порядок определения:
Прочитать 12 байт и побайтно сравнить сигнатуру. Если все байты равны
0x00или0xFF, заголовок не записан.По байту
versionвыбрать структуру: версия 1 — 512 байт, версии 2, 3 и 4 — 256 байт.Неизвестная версия — ошибка разбора.
Заголовок версии 4
Текущая версия заголовка — 4.
Раскладка заголовка версии 4. Строки — по 16 байт, адрес строки указан слева. Зеленым отмечены сведения о плате, желтым — значения, из которых строится строка, подписываемая подписью устройства (см. Проверка цифровой подписи).
Смещение |
Размер |
Поле |
Описание |
|---|---|---|---|
0–7 |
8 |
|
Сигнатура |
8 |
1 |
|
Версия заголовка: 4 |
9 |
1 |
|
|
10 |
1 |
|
Номер версии файловой системы, текущая версия — 1 |
11 |
1 |
— |
Резерв |
12–43 |
32 |
|
Название платы |
44–75 |
32 |
|
Версия платы |
76–107 |
32 |
|
Серийный номер платы |
108–139 |
32 |
|
Внутренний идентификатор устройства (USID) |
140–171 |
32 |
|
Идентификатор процессора |
172–177 |
6 |
|
MAC-адрес, 6 байт в двоичном виде |
178–179 |
2 |
— |
Резерв |
180–243 |
64 |
|
|
244–251 |
8 |
|
Время записи заголовка: знаковое 64-битное число, секунды Unix-времени |
252–255 |
4 |
|
CRC32 байтов 0–251 |
Назначение полей:
board_serial— серийный номер этой платы. Серийный номер устройства в целом хранится в записиdevice.id.usidиcpuidзаполняются, если у платы есть такие идентификаторы, иначе поля нулевые. У контроллеров серии JXDusid— строка из 30 символов,cpuid— заводской MAC-адрес микроконтроллера ESP32 в видеXX:XX:XX:XX:XX:XX.timestamp— момент записи заголовка. Значение 0 означает, что время не задано.
Заголовок, у которого все байты от смещения 12 до поля crc32 нулевые, — пустой: он резервирует место под заголовок, но данных платы не содержит.
Чтение заголовка
Определить версию по первым 12 байтам.
Сравнить CRC32 байтов 0–251 со значением в байтах 252–255.
Извлечь строки: до первого нулевого байта, а если его нет — все поле целиком.
Прочитать
fs_version— номер версии файловой системы, которая описана ниже. Незнакомая версия — ошибка разбора.Ненулевые байты в резервных полях игнорировать.
Файловая система
Версия файловой системы записана в байте fs_version заголовка платы; текущая версия — 1. Файлы образуют однонаправленную цепочку, которая начинается сразу за заголовком платы: со смещения 0x0100, для заголовка версии 1 — 0x0200. Каждый файл — это заголовок файла и следующие за ним данные.
Заголовок файла
Смещение |
Размер |
Поле |
Описание |
|---|---|---|---|
0–15 |
16 |
|
Имя файла: до 15 печатаемых символов ASCII, завершается нулевым байтом |
16–17 |
2 |
|
Размер данных: от 1 до 32767 байт |
18–21 |
4 |
|
CRC32 данных файла |
22–23 |
2 |
|
Смещение следующего файла от начала EEPROM; |
24–27 |
4 |
|
CRC32 байтов 0–23 заголовка файла |
Правила цепочки
Данные файла со смещением
Aи размеромDзанимают байты сA+28поA+28+D-1, следующий файл начинается сA+28+D. У последнего файла в цепочке полеnextFileAddressравно0x0000или0xFFFF, у остальных файлов оно обязано содержать именно это вычисленное значение; другое значение означает повреждение.Файл хранится непрерывно. При удалении файла последующие файлы сдвигаются на его место, освободившийся конец заполняется
0x00.Файлы расположены в порядке добавления. Исключение — файл
device.id: он всегда первый.Место свободно, если первый байт имени равен
0x00или0xFF. Записанное имя при неверномheaderCrc32означает повреждение, а не свободное место.headerCrc32проверяется при обходе цепочки для каждого заголовка,crc32данных — при каждом чтении файла.Смещения 16-битные, поэтому объем памяти не превышает 64 КБайт.
Идентификационные данные устройства
Устройство может состоять из нескольких плат, у каждой своя EEPROM. Модель, серийный номер и аппаратная ревизия устройства в целом хранятся отдельно от данных платы — в файле device.id, чтобы идентификационные данные устройства сохранялись при замене платы в ремонте.
Файл device.id всегда первый в цепочке, поэтому загрузчик получает данные устройства, прочитав 540 байт: заголовок платы (256), заголовок файла (28) и запись (256). С заголовком версии 1 — 796 байт.
Формат записи device.id, 256 байт:
Смещение |
Размер |
Поле |
Описание |
|---|---|---|---|
0–7 |
8 |
|
Сигнатура |
8 |
1 |
|
Версия записи: 1 |
9 |
1 |
|
Алгоритм подписи устройства: 0 — подписи нет, 1 — ECDSA secp192r1, 2 — ECDSA secp256r1 |
10–11 |
2 |
— |
Резерв |
12–43 |
32 |
|
Модель устройства |
44–75 |
32 |
|
Серийный номер устройства |
76–91 |
16 |
|
Аппаратная ревизия: числа через точку с необязательной буквой варианта исполнения, например |
92–93 |
2 |
|
Флаги, все биты зарезервированы: записывается 0, при чтении игнорируется |
94–179 |
86 |
— |
Резерв |
180–243 |
64 |
|
Подпись устройства: пара чисел |
244–251 |
8 |
|
Время создания записи, секунды Unix-времени |
252–255 |
4 |
|
CRC32 байтов 0–251 |
Строковые поля записи — строки фиксированной длины. Хвост записи (signature, timestamp, crc32) расположен по тем же смещениям, что и в заголовке платы. Запись защищена двумя контрольными суммами: своей и CRC32 данных файла. Поэтому ее можно проверить и внутри файловой системы, и отдельно извлеченной.
Правила чтения записи:
Буфер из одних
0x00или0xFFозначает, что записи нет.record_version, отличный от 1, и неизвестныйsignature_version— ошибка разбора.
Подпись записи — подпись устройства. Что она подписывает и как ее проверить, описано в разделе Проверка цифровой подписи.
Предыдущие версии заголовка
Библиотека jeefs разбирает все версии заголовка.
Версия |
Размер |
Отличия от версии 4 |
|---|---|---|
1 |
512 байт |
Полей |
2 |
256 байт |
Полей |
3 |
256 байт |
Раскладка совпадает с версией 4, поле по смещению 76 называется |
В версиях 1 и 2 байт 10 описан как часть резерва, но он всегда остается байтом fs_version: запись файловой системы проставляет в него номер версии и пересчитывает контрольную сумму заголовка. При перезаписи заголовка этот байт нужно сохранять.
Библиотека jeefs
Библиотека jeefs реализует формат на нескольких языках. Реализации проверяются общим набором тестов и эталонных образов.
Язык |
Установка |
Возможности |
|---|---|---|
Python |
|
Заголовки, запись |
Rust |
|
Заголовки, запись |
C, C++17 |
CMake-пакет |
Заголовки, запись |
Разбор образа EEPROM на Python:
from pathlib import Path
from jeefs import parse_image
image = parse_image(Path("eeprom.bin").read_bytes())
header = image.header
if header is None: # the Python package does not parse v1 and v2 fields
print(f"v{image.version}: header fields unavailable")
else:
print(f"v{image.version}", header.boardname, header.boardversion, header.mac)
for file in image.files:
print(file.name, len(file.data))
Функция parse_image выдает исключение ValueError, если заголовок платы не найден или поврежден, цепочка файлов нарушена или версия файловой системы неизвестна. Файлы с неверной контрольной суммой данных перечислены в image.unreadable. У образов с заголовками версий 1 и 2 image.header равен None: поля этих версий Python-пакет не разбирает, их читает реализация на C.