Возвращаем к жизни калькулятор HP Prime G2

iw8hsiguw6kpfi6rjwzdhqcvy3q.jpeg
Калькулятор с восстановленной прошивкой.

В предыдущих сериях:


Если вы читали серию моих опытов с калькулятором, то вероятнее всего, помните, что в попытке сделать бекап оригинальной прошивки, я её окончательно и бесповоротно стёр. Далее, после всех моих экспериментов, калькулятор был сложен в коробочку, и в ней даже пережил переезд, но с тех пор не покидал её. Поскольку линукс ещё требовалось допиливать до рабочего состояния, а энтузиазм уже совершенно закончился. Да и не очень понятно, какое практическое применение калькулятора с линукс, разве что девчонкам показывать, что там работает doom. В целом, весь проект был для того, чтобы освежить в памяти сборку и установку линукс на подобные устройства, дабы мозги не ржавели.

В результате, когда в очередной раз наткнулся в шкафу на лоток с этим барахлом, принял волевое решение вернуть его к жизни, в качестве калькулятора, каким он и был в девичестве.
Как обычно, путь был тернист, даже был сформирован мердж реквест автору перепрошивки линукса, но обо всём по порядку.

▍ Исходники и особенности работы с nand-флеш


На руках у меня был полуразобранный калькулятор, инструментарий для перепрошивки, образ флешки от zephray (который оказался не рабочим), и рьяное желание всё восстановить.

pgeyqaqk62qoa8ci841qdqijqow.jpeg
Лохань с запчастями.

Главный вопрос, который может задать читатель: если у тебя есть образ флешки, пусть не твоей, то что мешает, просто её записать и получить работающий калькулятор?
Мешает один нюанс, о котором многие даже не подозревают. В таких устройствах, часто используется nand-флеш, которая не так проста, как кажется. Сколько копий было сломано при работе с данным типом памяти, сколько крови она выпила у разработчиков, не скрою, очень рад что они постепенно отмирают.

Не буду подробно останавливаться на устройстве nand-памяти, если кому интересно, могут почитать, например, вот тут. Однако, проясню несколько важных моментов. Вся память разбита на страницы, страницы объединены в блоки. Чтение/запись идёт постранично, стирание поблочно.

image-loader.svg
Структура nand-памяти.

Чтобы понять, что у нас за память, загрузим u-boot и выполним команду:

=> nand info
Device 0: nand0, sector size 128 KiB
  Page size       2048 b
  OOB size          64 b
  Erase size    131072 b
  subpagesize     2048 b
  options     0x40004200
  bbt options 0x00068000


Erase size  — нам показывает размер блока, он равен 2048 страницам (131072 /2048). Но на самом деле, эти данные нас не особо волнуют, самое главное — это размер страницы и магическая аббревиатура OOB, которая равна 64 байтам!

Если вы посмотрите на картинку, то увидите, что у каждой страницы ещё есть 64 байта OOB области. Процитирую описание с сайта, что такое OOB:

Каждая страница соответствует области, называемой резервной / избыточной областью. В системах Linux это обычно называется OOB (Out Of Band). Эта область изначально основана на оборудовании Nand Flash. Особенности: В данных относительно легко делать ошибки при чтении и записи, поэтому для обеспечения правильности данных должен быть соответствующий механизм обнаружения и исправления ошибок. Этот механизм называется ECC (исправление кода ошибки или проверка и исправление ошибок), поэтому конструкция является избыточной. Область используется для размещения контрольного значения данных.

Операции чтения и записи OOB обычно выполняются вместе с операциями со страницами, то есть при чтении и записи страниц oob читается и записывается соответственно.

Конкретные варианты использования oob резюмируются следующим образом:

1. Является ли отметка плохим блоком
2. Хранить данные ECC
3. Храните некоторые данные, относящиеся к файловой системе.


Проще говоря, у каждой страницы, размером 2048 байт (на нашей флешке) существует специальная область памяти OOB, размером 64 байта, в которой могут храниться специальные данные, в том числе о bad-блоках (как правило, первые два байта в OOB равны нулю). Она также используется для работы файловой системы.

И, логично предположить, что в операционной системе калькулятора эта область должна использоваться и как-то применяться, хотя бы для того, чтобы помечать битые сектора, а как максимум для файловой системы. Кстати, это одна из особенностей NAND, что на ней нехорошо использовать популярные файловые системы, а стоит использовать только те, которые предназначены для работы на NAND-флешках, например, jffs2. Вообще, по NAND в пору писать отдельную статью.

Короче говоря, если у нас страница равна 2048 байт, а OOB область равна 64 байтам, то если мы делаем бекап 1 МБ NAND флеш, то он должен занимать объём равный 1081344 байт! Мегабайт обычных данные занимают 1048576 байт:

  • 1081344 данные на NAND (2048 байт страница + 64 байта OOB)
  • 1048576 обычные данные.


Считается достаточно просто, значение размера делится на 2048 и умножается на (2048 + 64).
Вернёмся к образу nand, который мне дал zephray.

image-loader.svg

▍ Размер бекапа от zephray


Мы знаем, что флешка у нас 512 МБ (хотя на самом деле, я в этом не уверен, думаю что она 1 Гб). И да, размер этого файла ровно 512 МБ:

$\frac{536870912}{1024\cdot1024}=512$


Но, как я уже сказал, размер должен быть 553648128 байт. То есть его без разделов OOB. Конечно же, я его попробовал, но увы, он оказался нерабочим.

▍ Доработка скриптов бекапа и восстановления


Стало понятно, что мне требуется донор, откуда можно снять прошивку. Понятное дело, что вероятнее всего, калькулятор мне не дадут, поэтому нужно подготовить архив со скриптами, чтобы человек по инструкции смог бы всё повторить.

Для начала, я решил проверить работоспособность скриптов, которые идут у zephray вот тут: github.com/zephray/prinux.

При проверке скрипта restore_nand.uu столкнулся с тем, что скрипт валится на строчке:

FBK: ucmd gzip /tmp/0-64mb.bin


Это строка создания архива, с чем связано — неясно, но я предположил, что нехватает ОЗУ, хотя zephray заверил меня, что такого быть не может. На самом деле, создание архива, операция не очень нужная, в результате из скриптов backup_nand.uu и restore_nand.uu удалил операцию упаковки и распаковки архивов. Даже не поленился и сделал pull request в основной репозиторий и он был одобрен. Всегда приятно поправить чужой код.
Сейчас можно посмотреть обновлённую версию скрипта.
Протестировал, погонял, оказалось, всё работает. И сформировал архив для того, чтобы можно было его передать тому, кто сможет мне сделать бекап.

▍ Делаем бекап или хождение по граблям


На мою радость, товарищ gears имеет точно такой же калькулятор. Вообще, изначально мы вместе планировали воевать с линуксом, но потом как-то не срослось. И я обратился к нему с просьбой, мог бы он пройтись по моей инструкции, установить утилиту uuu, затем разобрать калькулятор, замкнуть нужные контакты пинцетом и запустить скрипты из архива, что я ему прислал. И в ответ мне выслать содержимое папочки backup.

На деле всё не так страшно, самое сложное — это разобрать калькулятор.
В результате gears по моей просьбе установил linux, собрал uuu и попробовал выполнить:

sudo uuu ./my_backup_nand.uu


Но всё зависало на моменте:

oj_fecv7q_yhfhlja7wx2_ukgcg.jpeg

По скрипту это означало следующее: убут, ядро, devtree, rootfs успешно загрузились в ОЗУ, дальше идёт джамп на u-boot и ничего не происходит. То что ядро не стартануло, говорит о том, что светодиод не мигает. Даже если там были какие-то проблемы, то ядро всё равно мигало бы светиком.

Несколько дней мозгового штурма, отрицания, гнева, принятия. Был практически на грани того, чтобы приехать лично и попробовать самостоятельно провернуть эту процедуру. Но gears высказал здравую мысль, что у нас отличается версия uuu.

image-loader.svg

Изначально я её отмёл, как маловероятную, но практика показывает, что если у тебя что-то работает, то если ты хочешь, чтобы работало у кого-то ещё, создай аналогичные условия, убрав все возможные факторы отличия. И потом уже разбирайтесь в чём проблема.
В результате, не поленился и начал смотреть, когда же была собрана моя утилита. Моих исходников не сохранилось, утилита не даёт хеша коммита, с которого была собрана, но на гите удалось найти версионность и сопоставление хешей.

По итогу дал такую инструкцию, чтобы сделать мою версию, надо собрать на бранче от хеша:

git checkout 63b1d3c
git checkout -b new


И далее сборка.

И, да! У нас всё получилось!

ub5wol6za190jmujtjiikbj3aci.jpeg
Скрин с файлами, удачного бекапа!

Спустя несколько минут, мне прислали архив бекапа, который я радостно прошил.
Из соображения лицензирования (это таки пиратство, хотя я просто восстанавливал прошивку), не буду выкладывать прошивку в открытый доступ. Но если кому она будет нужна, то пишите мне, я помогу.
Обратите внимание, на этот, вроде бы малозначительный нюанс. Если кто будет повторять прошивку линукс, есть шанс, что версия uuu не позволит вам это сделать!

▍ Заливка оригинальной прошивки


Если посмотреть hex-редактором, то оказывается, что прошивка калькулятора занимает примерно первые 100 МБ. И если в этой области встретится битый блок (который, напомню, занимает 2048 страниц, или же 4МБ), то фокус уже не удался бы. Но у меня всё получилось.

Запускаю восстановление калькулятора:

image-loader.svg

И спустя некоторое время в результате получаю работающий калькулятор. Хотя запись последних мегабайт и привела к ошибке:

image-loader.svg

О причинах которой мы поговорим позже.

После включения он показал, что делал хозяин калькулятора, откуда мы делали бекап:

k91bq_ajtyowmjhhlfrbmpvstdk.jpeg
Наконец он заработал!

Восторг, который я испытал — не передать, он сравним примерно с тем же, когда мне удалось запустить на нём linux,
Но это ещё не всё, как я уже сказал, на NAND обычно существуют бэд-блоки, помеченные специальным образом. Когда я копирую чужой образ, я копирую чужие метки и затираю свои. Если первое, не так криминально, просто напросто теряю место на флешке, то второе гарантированно не будет работать, потому что эти области не могут быть записаны.
Поэтому необходимо найти способ, как внести данные о своих битых блоках в текущий образ.

▍ Поиск bad-блоков и исследования nand (немного кодинга)


Как я уже сказал, чтобы всё корректно работало, надо чтобы все реальные бед-блоки были на своих местах. Для этого я даже набросал небольшую программу, чтобы «ручками» промаркировать все битые блоки. Но для начала, стоит посмотреть реальную картину.
Первое, что я сделал — это перезагрузился обратно в u-boot, и посмотрел содержимое bad-блоков, какие я скопировал, какие удалил. После загрузки выполняю команду:

=> nand bad

Device 0 bad blocks:
  1ff80000
  1ffa0000
  1ffc0000
  1ffe0000


Хм… Странно, адреса бед блоков не изменились, ещё со времён установки doom. Там я тоже воевал с флешкой. Эти адреса находятся в конце флеш-памяти, например, 0×1ff80000 указывает на 511,5 МБ флеш-памяти, то есть практически в конце 512-ти мегабайтовой памяти.
И, вероятнее всего, прошивка вылетела с ошибкой, что у меня стояла опция прошивки:

nandwrite --noecc --noskipbad --oob -s 0xc000000 /dev/mtd0 /tmp/192-256mb.bin


--noskipbad означает — не пропускать битые блоки.
На самом деле, мне очень повезло, что удалось всё восстановить без проблем.

Что меня ещё смутило, что вывод nand dump 0 отличается от того, что я вижу в хекс редакторе по нулевому адресу. Выделил область, которая совпадает, но начало разное.

image-loader.svg
То, что видно в u-boot по нулевому адресу.

image-loader.svg
То что видно в хекс редакторе 0–64mb.bin.

Откуда взялись лишние 10 байт? При этом калькулятор успешно загружается и работает. Решил набросать небольшую программу, которая создаёт тестовую прошивку, чтобы проверить, что прошивается корректно.

Эту же программу я использовал для поиска битых секторов в снятой прошивке, но на самом деле способ пометок битых секторов у ОС калькулятора может отличаться от той, которая применяется в линукс. И я потерпел фиаско, писать об этом смысла не вижу.
Программа мапит файл в память, и далее работает уже как с областями памяти, где 2048 байт — это страница, а 64 байта после — это область OOB.
Определим структуру страницы памяти.

#define PAGE_SIZE 	2048
#define OOB_SIZE	64

struct nand_page {
	char page[PAGE_SIZE];
	char oob[OOB_SIZE];
};


И набросаем скелет программы:

int main (int argc, char* const argv[])
{
	int fd;
	struct nand_page * file_memory;
	struct stat statbuf;
	
	fd = open (argv[1], O_RDWR, S_IRUSR | S_IWUSR);
	fstat(fd, &statbuf)
	printf("Size of file = %lu\n", statbuf.st_size);
	printf("Pages in file = %lu\n", statbuf.st_size / sizeof(struct nand_page ));
	file_memory = mmap (0, statbuf.st_size, PROT_READ | PROT_WRITE,MAP_SHARED, fd, 0);
	
	//do something here
	
	munmap (file_memory, statbuf.st_size);
	close (fd);
	return 0;
}

Можно сделать функции, которые будут выводить данные в том же формате, что и метод nand dump в u-boot.

void print_page (struct nand_page * page) {
	int i,j;
	for (i=0;i<2048;i+=16) {
		printf("        ");
		for(j=0;j<16;j++) {
			printf("%02x ",page->page[i+j]  & 0xFF);
			if (7==j) {
				printf(" ");
			}
		}
		printf("\n");
	}
}

void print_page_oob (struct nand_page * page) {
	int i,j;
	for (i=0;ioob[i+j]  & 0xFF);
		}
		printf("\n");
	}	
}


Функция поиска бед-блоков у меня следующая (перекочевала из другого проекта):

int block_ram_is_bad (struct nand_page * page) {
	if ((0 == page->oob[0]) || (0 == page->oob[1])) {
		return 1;
	} else {
		return 0;
	}
	
}


Но к сожалению, на образе с калькулятора она дала осечку, и ничего не нашла.
Если вам приходится работать с образами nand, советую взять на заметку эти куски кода.
Точно так же я сделал функции, которые заполняют страницу памяти значениями от 0 до ff циклично и точно так же заполняют дополнительный раздел:

void write_page (struct nand_page * page) {
	int i,j;
	for (i=0;i<2048;i+=16) {
		for(j=0;j<16;j++) {
			page->page[i+j] = (i+j) & 0xFF;
		}
	}
}

void write_page_oob (struct nand_page * page) {
	int i,j;
	for (i=0;ioob[i+j] = (i+j) & 0xFF;
		}
	}
}


И заполняем файл в теле функции main:

	for (int i = 0; i < statbuf.st_size / (2048 + 64); i++) {
		write_page(&file_memory[i]);
		write_page_oob(&file_memory[i]);
	}


Далее я создал файл размером 1 МБ (вместе с OOB). И заполнил его значениями с помощью программы:

dd if=/dev/zero of=123.bin bs=1081344
./mmap-read 123.bin


В результате получился такой вот файл (вывод этой же программой, в формате u-boot):

image-loader.svg

image-loader.svg
Обратите внимание, что начинается с нуля и каждый следующий байт увеличивается на 1.

Далее заливаю этот файл модифицированным скриптом restore_nand.uu. Покажу лишь те строчки, которые изменились:

FBK: ucp clear/123.bin t:/tmp
FBK: ucmd nandwrite --noecc --noskipbad --oob -s 0x0 /dev/mtd0 /tmp/123.bin


Перезагружаю калькулятор в u-boot, и смотрю что же изменилось:

image-loader.svg

image-loader.svg
Обратите внимание, что первые 10 байт страницы содержат какой-то мусор, и первые 20 байт раздела OOB.

Короче говоря, как я понял, u-boot выводит на экран какую-то лабуду, и доверять ему в этом вопросе не могу. Потому что пишем мы одно, а потом читаем из nand другое. Возможно, таки первые байты чем-то забиты. Тут следовало бы скачать ещё раз файл обратно и сравнить с исходным, я так и сделал. Но полученный файл у меня вышел вообще одни 0xFF. В результате я сломался. Потому что не очень понятно, куда это исследование ведёт. Тем более что кривые u-boot мне попадались значительно чаще, чем стабильно работающие (увы, специфика).
Калькулятор работает, надеюсь, он никогда не полезет в битые области, и также надеюсь, что у него есть механизмы определения битых секторов и помечать их. В образе, который мне прислал gears средствами u-boot битых секторов обнаружить не удалось. В стёртой флешке, которую я скачивал к себе, на компе моей программой не удалось определить, какие страницы находятся в битых секторах. И тут непонятно, то ли лыжи не едут, то ли я делаю что-то не так.

▍ Окончательная сборка


Самая приятная часть этого проекта — собрать калькулятор обратно. Как вы помните по фотографиям, у меня из него выходили провода для подключения UART.

hybom9tajnhvlwv8rw-1syqbnw8.jpeg
Провода, идущие к макетке.

Решил, вдруг меня снова накроет, и решу доделать линукс на калькуляторе, либо передам это устройство в руки тех, кто захочет это повторить. Поэтому провода решено было оставить, но убрать платку. Тем более, они там хитро подпаяны. Чтобы не перепутать, какой провод куда идёт, пометил их малярным скотчем, который также играет роль изолятора.

v-nqjmq7_mthi735q_8wvxey4v8.jpeg
Маркировка проводов.

Внутри корпуса калькулятора полно свободного места, так что провода там влезут без проблем. Ставлю на место аккумулятор (удивительно, что за это время он совершенно не разрядился).

qenmmnthgjcglycjtsn1icgsdfs.jpeg
Перед установкой аккумулятора.

И опа, никто не догадается, что с этим калькулятором творили такие издевательства.

vnxomuwhaa7n1o6fuueytfvdaea.jpeg

▍ Выводы


Вот и завершился целый проект бесчеловечных опытов над калькулятором. Мне казалось, что вот так бросить в разобранном виде устройство — это качество не моей мануфактуры, и нужно всё довести до конца. Плюс, я теперь снова имею в хозяйстве очень крутой графический калькулятор, который могу использовать по прямому назначению. Ну и плюс освежил в памяти особенности работы с nand-памятью.

В предыдущих сериях:


image-loader.svg

© Habrahabr.ru