Проверка и оценка TAMS 3011, Часть 6: Начало саги о загрузке NetBSD на новом оборудовании

Если с первого раза не вышло…

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

Питер Сибах, автор, Независимый

Питер Сибах (Peter Seebach) работает с компьютерами много лет и постепенно приспособился. Хотя он до сих пор не понимает, почему мышку надо чистить так часто.



07.03.2007

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

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

Компилируя

Первый значительный шаг в создании работающего ядра - это сборка ядра с кодом, которое может делать хоть что-то. Все имеющиеся системы 405GPr находятся в директории arch/evbppc. (Если не указано ничего другого, все упоминаемые в статье директории находятся в /usr/src/sys/).

В этой статье используется тот же инструментарий с кросс-компилятором, предназначенный для NetBSD-систем, который описан в предыдущей статье "Porting NetBSD to the TAMS 3011," В принципе, всё это можно делать и в другой среде, например Cygwin, однако я использовал ту систему, которая у меня была.

Файлы настроек находятся в arch/evbppc/conf/, файлы включений в arch/evbppc/include/, а код для каждой платформы или каждого семейства в своей собственной поддиректории, такой как arch/evbppc/obs405/ (для систем openblocks 200 и 266), или arch/evbppc/walnut/ (для системы IBM "walnut".

Среди всех систем openblocks266 и walnut наиболее близко интегрированы с TAMS3011. Я использовал openblocks266. Первое, что мне надо было сделать, это создать новый файл конфигурации в соответствующей директории; я назвал его TAMS3011. В файл конфигурации ядра включены (с использованием ссылки посредством директивы "include") несколько других файлов; например, в файл конфигурации OPENBLOCKS266 включен файл std.obs405, в который в свою очередь включен файл files.obs405. Разница в использовании этих файлов почти неуловимая. В файл std.system включены опции, предположительно полезные и стандартные для всех похожих систем, вроде опции EXEC_ELF, тогда как в файл files.system включён перечень исходных модулей, специфичных для конкретных портов. Кроме того, есть ещё файл Makefile.system.inc, автоматически включаемый в Makefile при построении ядра. Таким образом я скопировал эти файлы вместе с основным файлом конфигурации, создав новые файлы TAMS3011, files.tams3011, std.tams3011, и Makefile.tams3011.inc.

Для первого запуска я просто закомментировал все вызовы openbios_*, которые работают с данными настроек, передаваемыми в загрузочную среду OpenBios. Мне нужен был заголовочный файл для определения таких вещей, как частота UART, и я создал tams3011.h. После всего этого я был готов к компиляции ядра и поиску ошибок.

Само собой, ошибки нашлись. Во-первых, компилятор С по умолчанию на моей системе x86 не способен компилировать для PowerPC®. Моим общепризнанно некрасивым решением было добавить в начало конфигурационного файла TAMS3011 несколько строк (их можно вставлять куда угодно, но обычно их ставят сверху):

makeoptions CC="powerpc--netbsd-gcc"
makeoptions AS="powerpc--netbsd-as"
makeoptions LD="powerpc--netbsd-ld"
makeoptions STRIP="powerpc--netbsd-strip"
makeoptions DBSYM="powerpc--netbsd-dbsym"
makeoptions COPTS="-O2"

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


Море красных флажков

Ядро NetBSD построено, по умолчанию, с большим количеством предупреждений. Что более важно, оно скомпилировано с флагом -Werror, который включает показ предупреждений к ошибкам; не разобравшись со всеми этими предупреждениями, мы не сможем скомпилировать код. Делая первый запуск, вырезая некоторый код и заменяя его, вы бережёте своё время; имея провалившуюся компиляцию с сообщением о том, что переменная, возможно, используется неинициализированной, вы спасаетесь от провалившейся попытки установить ядро на удалённую систему. Предупреждающие флаги вот:

Листинг 1: Капля профилактики стоит фунта лечения
	-Wreturn-type
	-Werror
	-Wall
	-Wpointer-arith
	-Wmissing-prototypes
	-Wstrict-prototypes
	-Wreturn-type
	-Wswitch
	-Wshadow
	-Wcast-qual
	-Wwrite-strings

Некоторые опции выключены: -Wno-sign-compare, -Wno-format-zero-length, and -Wno-main. Последняя подавляет предупреждение, генерируемое из-за того что, что у функции main() ядра нет подписи, которая допустима для основной функции в удалённой среде.


Вопрос времени

Сортируя предупреждения, наткнулся на вещи, которые при первом бессистемном запуске не заметил, например, не уточнённая частота UART. Вначале немного теории. Оборудование серийного порта управляется часами с некоторой фиксированной частотой, скажем, 25MHz, или 7MHz, или ещё какой-то. В некоторых системах есть отдельные часы ввода/вывода, в других для упрощения используется какой-нибудь экзотический делитель обычных часов. Серийный порт работает на заданной скорости, вычисляя какой делитель этой частоты будет наиболее приближён к указанной скорости. То есть, чтоб использовать серийный порт UART необходимо знать скорость часов.

Сначала я просто попытался скопировать значение с другого порта. Сделав это и загрузив ядро, я получил строку из бессмысленных символов; похоже, скорость не совпадала. Второй попыткой я скопировал значение из исходника ядра Linux® для этой платформы. Сначала показываю код:

Листинг 2: Запись IOCCC?
	#define BASE_BAUD (((400*1000000)/((0x3C>>1)+1))/16)

Могу объяснить часть с "400*1000000":скорость ЦП 3011 равна 400MHz. Вот я и попробовал. Безуспешно. В отчаянии я обратился к документации, где было написано что частота UART равна скорости процессора, делённой на 31. (Замечание: чтоб написать 31, не размещая слитно 3 и 1, можно написать так - ((0x3C>>1)+1).) И вот я попробовал 400 миллионов поделить на 31… И это сработало. Сработало - слишком сильно сказано, я запаниковал, увидев первых 5 строчек, однако консоль работала.

Также замечу, что, помещая магические числа во встроенные системы, надо следить за типами данных. Известны случаи, когда некое значение пытались помещать в довольно ограниченный регистр, и когда оно не вмещалось, это служило поводом для удивления. Поскольку вы можете начинать с больших значений (например, 400 миллионов, как тут), то и простор для ошибок будет немаленьким. Об этом легко забыть, особенно когда у вас есть несколько уровней генерирующих константу вложенных макросов.

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

Листинг 3: Найдем вычисленное значение
{
uint32_t dummyval;
dummyval = BASE_BAUD;
}

Функция board_info, раунд 1

Функция board_info - это небольшая БД со свойствами (использующая встроенную в ядро NetBSD поддержку баз данных), которая используется на многих портах 405GPr для хранения данных о системе. Первые необходимые данные - это объём памяти и частота процессора. Я схитрил, попросту закодировав это значение в загрузочные функции.

На системах OpenBios много полезных загрузочных данных доступны ядру через указатель, хранимый в r31. Загрузочный код сохраняет, а затем копирует эту структуру данных. Когда система загрузилась настолько, что можно начать размещение памяти, отсюда функцией openbios_board_info_set() заполняется БД свойств. (Находится в arch/powerpc/ibm4xx/openbios/openbios.c.) Вопреки изначальному моему предположению, что всей этой кучей кода можно пренебречь, он используется в нескольких местах для получения информации, которая нужна всему остальному ядру. Первые два использующиеся кусочка информации - это объём памяти и частота процессора.

Установить эти два параметра напрямую довольно просто:

Листинг 4: Установка скорости процессора
	unsigned int processor_speed = 400 * 1000 * 1000;
	board_info_init();
        board_info_set("processor-frequency",
		&processor_speed, sizeof(processor _speed), PROP_CONST, 0);

Этот код был в точности скопирован из рутины openbios_board_info_set, которая заполняет некоторые другие значения точно таким же способом. Однако в ней есть ошибка. Сначала заявив, что система работает на частоте 400MHz и посчитав количество циклов на один отсчёт ядра (100MHz по умолчанию), он заявляет, что их 0. Почему? Потому что код в часах считает, что ядро работает на 26Hz, и коль скоро оно работает на 26Hz, а у вас 100 прерываний в секунду, получается меньше одного цикла на прерывание.

Оригинальный код OpenBios использует записи из статичной структуры ядра для хранения данных, сохранявшихся board_info_set. Макрос board_info_set - это оболочка над интерфейсом NetBSD propdb, и он выдаёт то, что указано в PROP_CONST, и хотя это значение может быть изменено, в коде propdb должен храниться только указатель, а не выделяться место под копию. Поскольку данная переменная локальная, запись в БД содержит "что угодно, что сейчас хранится в этой части стека ". Объявление переменной статичной было простейшим методом исправления этой ошибки.

С настроенной частотой процессора и объёмом памяти ядро оказалось на удивление рабочим. Работала даже часть устройств, однако сетевые платы в их число не входили.


Получение MAC адреса (board_info, раунд 2)

В установочной информации, приходящей от OpenBios, на системе OpenBios, включён хотя бы один MAC адрес для находящегося на 405GPr контроллера emac0. У контроллера нет встроенного MAC адреса, ос должна запрограммировать его при помощи информации из загрузочной EEPROM. Поскольку RedBoot это уже сделал, этот шаг я пропускаю. Моим первым решением проблемы, заключавшейся в сообщении, что MAC адрес отсутствует, была попытка считать его с микросхемы. Что дало мне 00:00:00:00:00:00 в ответ, приведя к открытию, что после перезагрузки микросхемы MAC адрес был удалён. Второй попыткой я модифицировал процедуру перезагрузки, она стала считывать MAC адрес, перезагружать микросхему, и восстанавливать адрес. Получилось уродливо. Однако достаточно эффективно для запуска контроллера, так что это был реальный шаг в сторону правильного решения. (Тот факт, что все эти изменения позже пришлось уничтожить, не значит, что прогресса не было.)

Так выглядит код для записи на чип MAC адреса (хранимого в шестибайтовом массиве):

Листинг 5: Запись сетевого адреса
	EMAC_WRITE(sc, EMAC_IAHR, enaddr[0] << 8 | enaddr[1]);
	EMAC_WRITE(sc, EMAC_IALR,
	    enaddr[2] << 24 | enaddr[3] << 16 | enaddr[4] << 8 | enaddr[5]);

Существующий код выполнял следующее. Мой обходной путь заключался в операции чтения для получения адреса в начале перезагрузки, потом записи данных обратно; потом в возвращении к началу драйвера для проверки, что я туда записал. Как говорят разработчики, это не было чистым решением.

У сетевой карты DS83816 на этой системе нет места для хранения MAC адреса. У большей части сетевых карт 83815/83816 есть своя EEPROM (как и у большинства остальных сетевых MAC плат); но не у этой. Несколько минут я пытался во всём этом разобраться, а потом получил письмо от инженера TAMS, который невзначай упомянул что на плате 83816 в этой системе отсутствует EEPROM. Что делать? Использовать имеющуюся конфигурацию. Поскольку мой обходной путь прекрасно работал на контроллере emac0, я приспособил такой же код и для драйвера sip0. Всё равно ничего не достигнув и потеряв ещё немного сил и времени, я решил заняться этим позже, и это оказалось наилучшим решением из всех вышеописанных.


Сетевая файловая система и часы в режиме реального времени.

Имея рабочий интерфейс emac0, было довольно просто добиться достаточно успешной сетевой загрузки для обслуживания файловой системы на 3011. Пришлось немного поизворачиваться, и хотя часы в реальном времени не работали, я смог открыть оболочку и запустить простенькие команды. Научившись на своём опыте с UART, я углубился в документацию. Оказывается, каким-то образом я убедил себя в том, что RTC является частью процессора; на самом деле это не так, я же поместил в файл конфигурации неправильную строку для часов. Я установил правильные часы (""dsrtc0"в часах DS1307) в моём файле конфигурации, и это сразу сработало. Единственной необходимой настройкой оставался адрес I2С, который я нашёл в документации к 3011.

Теперь убедившись, что шина I2C работает, я подумал, что было бы неплохо взглянуть на ещё одно устройство на этой шине, 4KB EEPROM, хранящую настройки для загрузки. Это оказалось на удивление просто благодаря наличию драйвера для плат AT24Cxx. Единственной проблемой было то, что я недопонял, что значит примечание "addr N" для устройств на шине I2C, и благодаря этому потратил около часа на копание в микросхеме, содержащей 4 килобайта восьмибитовых байтов. Теперь у меня получилось прочитать данные. Однако…


Вниз по кроличьей норе

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

Это сработало, и даже пропала надобность в сохранении и восстановлении MAC адреса при перезагрузке (что было несомненно плохой идеей), однако всё равно чувствовалось что-то не то. Пока я думал о возможной перенастройке датчиков, в голову пришла мысль посмотреть, как такое сделано в исходниках Linux.

В исходниках Linux я обнаружил, что эта информация извлекается из структуры с типом, определённым в рядом находящемся заголовочном файле. Более того, структура была объявлена в файле walnut.h, что навело меня на мысль о том, что она такая же и в Walnut - структура в конфигурации OpenBios, для которой в NetBSD уже имелся код!

Я провёл несколько часов в попытках найти и достать эти данные, экспериментировал с разными вещами. Получалось так, что вся строка запроса была неправильной; там были разные типы данных. На большей части IBM4xx платформ под управлением Linux определена структура типа bd_t. Однако в некоторых используется тип, определённый где-то в ядре, и некоторые определяют свои собственные типы, с разными планировками. Фактически кодом tams_moab используется не OpenBios-овская, а RedBoot-овская версия структуры.

Однако сильнее всего меня смутил процесс инициализации. Ниже приведена инициализация объекта bd_t, найденная в исходниках Linux для нескольких разных платформ на базе IBM PowerPC 4xx-series:

Листинг 6: Инициализируемые переменные посходили с ума
	bd_t *bip = (bd_t *) __res;
	bd_t *bd = (bd_t *)&__res;
	bd_t __res;
	unsigned char __res[sizeof(bd_t)];
	bd_t *bip = &__res;
	extern bd_t __res;

Эти примеры из разных файлов, однако возникает впечатление, что есть стандартный тип bd_t, экземпляр которого хранится во внешнем объекте __res. Здесь не хватало только знания, где инициализируется объект __res, однако я случайно обнаружил, что инициализация происходит от значения, передаваемого в ядро от r31. Чрезвычайно похоже на то, как делается в OpenBios.

К сожалению, это оказался лишь отвлекающий манёвр. В RedBoot оказался специальный код для загрузки информации в определённую структуру (не такой, как в OpenBios) в памяти и для передачи адреса в ядро при использовании ядра Linux. Мысль, что я могу просто взять информацию из OpenBios и использовать её, оказалась ошибочной.

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

Ресурсы

Научиться

Получить продукты и технологии

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux
ArticleID=200362
ArticleTitle=Проверка и оценка TAMS 3011, Часть 6: Начало саги о загрузке NetBSD на новом оборудовании
publish-date=03072007