Тема: Прошивка сетевой платы НИИВК
Понадобилось тут посмотреть отличия прошивок экспериментальной и серийной сетевых плат НИИВК. Ну, а раз уж начал диазассемблировать, то решил изучить этот код повнимательнее и разобраться с протоколом обмена. Любопытно же, что там за ответ на TCP/IP внутри :) Вот что удалось выяснить на данный момент.
Прошивка здоровая, занимает все 2 КБ ПЗУ. К сожалению, целиком ее разобрать трудно. Там две части: одна отвечает за начальную загрузку машины, а вторая за обмен по сети в уже загруженном состоянии. С первой-то частью проблем нет, туда Монитор управление передает и дальше прошивка работает сама. А вот вторую часть должна вызывать операционка. Чтобы понять, что, как и когда она вызывает, ее саму надо сначала дизассемблировать, что непросто и небыстро.
Поэтому пока напишу про начальную загрузку.
Важное замечание: сетевые платы могут работать только на девятке. Прошивка использует адреса ее Монитора и активно работает с девяточным диспетчером памяти. Когда я это выяснил, сразу стало понятно, почему не делали апгрейд семерок. Ведь в школах было много классов семерок, причем, дисководы на них, к моменту начала выпуска сетевых плат, были уже довольно убитые. Почему бы не дать вторую жизнь этим классам, поменяв дисководы на сетевые платы и поставив на учительскую машину дисковод 840К? А вот нет, оказывается, сетевые платы были заточены под девятки.
Кстати, в статье "Функциональные возможности локальной сети для ПЭВМ Агат" в "Информатике и образовании" семерки упоминаются. Может, была версия прошивки для семерок? Или авторы немного приукрасили реальность?
Второе замечание: думаю, всем более-менее известно, как устроена прошивка прошивка контроллера дисковода. Она читает один (обычно один) сектор с диска, кладет его по определенному адресу и передает управление. Ей ничего не нужно знать ни про конфигурацию машины, ни про то, какую операционку с ее помощью будут загружать.
Так вот, прошивка сетевой платы совсем не такая. Первым делом она вызывает код обнаружения оборудования. Этот код опрашивает все платы расширения в машине и запоминает ее конфигурацию в таблицах устройств. Диспетчер памяти и все обнаруженные модули ОЗУ инициализируются в начальное состояние. Кстати, сам код обнаружения оборудования мне показался знакомым. Это оказался код из загрузчика ИКП-90. Прямо байт в байт.
Потом делается некий трюк. В самом начале загрузки прошивка переносит кусочек себя в ОЗУ по адресу $9A20. А после определения оборудования делает на этот кусочек переход. Я так понял, это защита от попытки перезагрузки машины командой Монитора CX00G. Если операционка уже была загружена, значит распределение страниц в диспетчере памяти поменялось, и этот кусочек кода попадет в другую страницу. Потом код обнаружения оборудования восстановит начальное распределение страниц и произойдет... переход в никуда. Перезагрузки не будет.
Дальше код по адресу $9A20 делает хитрый вызов Монитора по адресу $FAA0, и управление наконец-то попадает на код загрузки из сети. В этом месте прошивка очищает экран, выводит зеленые надписи "Сеть-АГАТ N_xx", где xx - сетевой адрес платы, и текст "Жду загрузку !", после чего переходит в цикл загрузки.
В цикле загрузки делается следующее: сбрасывается и программируется КР580ВВ51, ожидается поступление синхросимвола, после чего читается заголовок пакета, определяется тип пакета и загружаются данные. Если пакет не последний, то цикл повторяется сначала (включая сброс и программирование КР580ВВ51). Если пакет последний, то возможны два варианта: сразу перейти на адрес $6200, либо выполнить обновление таблиц устройств (об этом скажу дальше) и сделать переход на адрес $6200. Выбор варианта определяется значением поля в заголовке последнего пакета.
При загрузке КР580ВВ51 программируется в синхронный режим с размером слова 8 бит, четной четностью, и двойным синхросимволом. Синхросимвол - это слово, которое КР580ВВ51 автоматически мониторит в поступающих данных и сигнализирует о его обнаружении процессору через вывод SYNDET. На плате этот вывод генерирует сигнал IRQ. Синхросимвол нужен примерно для того же, для чего нужен синхросбой на дискетах - чтобы понять, где находится граница байта. КР580ВВ51 может использовать одинарный или двойной синхросимвол. В прошивке используется двойной. Первый байт синхросимвола всегда равен $81, второй байт - это сетевой адрес платы.
Есть странность - обработчик прерывания не устанавливается, а ожидание синхрослова делается циклическим опросом регистра статуса КР580ВВ51. Загадка в том, почему прерывание никогда не вызывается. Возможно, дело в том, что каждое чтение слова состояния сбрасывает сигнал SYNDET, а сам опрос делается часто (цикл выполняется за 9 тактов, а время передачи одного бита по сети - 16 тактов). Но не может же так быть, чтобы появление синхросимвола всегда совпадало с чтением регистра статуса? Явно тут какая-то хитрость с КР580ВВ51.
После синхросимвола идет заголовок пакета. Он начинается с байта $96. После этого процедура чтения заголовка ждет еще два байта $0 и $FF. Причем ждет она их хитро: если она получает какой-то другой байт, то она просто уменьшает счетчик на единицу и продолжает ждать нужные байты в правильном порядке. По аналогии с дисководами, я бы сказал, что это разделительный интервал (GAP). Процедура может пропустить до 1022 ненужных байт пока ждет нужные.
У меня два предположения, зачем так сделано. Скорее всего, это связано с наличием задержек на передающей стороне. Например, сервер (учительская машина) синхросимвол отправил, а данные для отправки еще не готовы. И передающая КР580ВВ51 просто шлет нули, пока данные готовятся.
Второе предположение - это защита от ситуации, когда синхросимвол есть в передаваемых данных. В идеале синхрослово не должно встречаться в данных, но это решает не микросхема, а программист. Можно было бы перекодировать данные так, чтобы байт $81 в них не встречался, но здесь этого сделано не было. И если начало пакета было определено неправильно, то делается попытка подождать, пока появятся байты $0 и $FF.
После байтов $0 и $FF идет любопытный байт. Я отследил все его перемещения, и оказалось, что он сохраняется... в ячейку номера трека. Напомню, что сетевая плата "прикидывается" контроллером НГМД. А Агатовские контроллеры не имеют регистра с текущим номером трека. Из-за этого про текущий трек приходится "помнить" операционке (RWTS). И чтобы после загрузки байт номера трека имел правильное значение, он передается в каждом сетевом пакете. Прикольно. Но логично, потому что в сети могут быть десятки машин и у каждой "дисковод" может стоять на разных треках.
Следующий байт - это тип пакета. Допустимых значений всего два: 0 и 1. Вообще говоря, это скорее признак "последний пакет/не последний пакет". Но поскольку структура данных пакетов отличается, то назовем его типом.
Пакет типа 0 - это обычный пакет данных. Он начинается с трех байтов. Первый - это адрес диспетчера памяти. Перед приемом каждого пакета делается обращение к девяточному контроллеру памяти по адресу $C1XX, где XX - значение байта. Следующие два байта - это адрес загрузки данных. Далее идет 256 байт данных - как раз один сектор. Контрольной суммы нет.
Пакет типа 1 похож на пакет 0, но содержит в начале три дополнительных байта. После приема этого пакета происходит запуск загруженного кода. По смыслу - это последний пакет. Первый байт определяет, как произойдет запуск: если он равен 3, то сразу будет выполнен переход на адрес $6200, при любом другом значении сначала будет выполнено обновление таблиц устройств, а затем будет выполнен переход на адрес $6200. Кроме того, этот байт одновременно сохраняется в ячейку номера дисковода. В таблицах устройств дисководы, подключенные к контроллеру нумеруются с нуля. Так что, видимо, допустимые значения этого байта 0, 1 и 3. Назначение следующих двух байт неясно. Они сохраняются, но не используются. Похоже, это должен был быть адрес запуска, но в прошивке адрес запуска фиксирован.
Спрашивается, а что это за таблицы устройств такие и зачем их обновлять? Отвечаю. У пакета ИКП есть дисковый загрузчик. При запуске он определяет конфигурацию машины и сохраняет информацию обо всех найденных устройствах в таблицах. После того, как в меню выбирается какой-нибудь компонент пакета (например, Рапира или Бейсик), загрузчик загружает все его части в память, а потом обновляет таблицы устройств. Это обновление состоит в определении числа дисководов (ИКП поддерживает до двух контроллеров НГМД по два дисковода на каждом) и инициализации некоторых плат (принтера, стыка С2). А после этого данные из таблиц используются для настройки загруженного компонента, потому что каждый компонент ИКП содержит свою собственную RWTS и DOS.
Так вот, прошивка полностью повторяет работу ИКП: она вначале определяет конфигурацию, а после загрузки ее обновляет. Более того, поскольку при обновлении таблиц делается опрос дисководов (а для этого нужна RWTS загрузчика ИКП), то считается, что RWTS загрузчика в память загружена и делаются ее вызовы.
Можно подвести некоторые итоги.
1. Протокол загрузки и прошивка позволяют загрузить операционку целиком. Загрузчик позволяет настраивать диспетчер памяти, адреса загрузки ничем не ограничены, количество пакетов тоже.
2. Прошивка сильно заточена под ИКП-90. Разработчики оставили, конечно, возможность грузить что-то другое, но даже в этом случае надо помнить, что данные о конфигурации машины лежат там, где их обычно держит загрузчик ИКП и конфигурация памяти такая же, как при загрузке ИКП.
3. Структура данных пакетов.
Заголовок
+---------+----------+--------------------------+
! № байта ! Значение ! Примечание !
+---------+----------+--------------------------+
! 0 ! $81 ! Синхросимвол 1 !
! 1 ! $xx ! Синхросимвол 2, равен !
! ! ! адресу принимающей машины!
! 2 ! $96 ! !
! GAP ! любое ! От 0 до 1022 байт !
! 3 ! $00 ! !
! 4 ! $FF ! !
! 5 ! $xx ! Номер трека !
! 6 ! $xx ! Тип пакета (0, 1) !
+---------+----------+--------------------------+
Пакет типа 0
+---------+----------+--------------------------+
! № байта ! Значение ! Примечание !
+---------+----------+--------------------------+
! 0 ! $xx ! Страница диспетчера !
! ! ! памяти !
! 1 ! $xx ! Адрес загрузки пакета !
! 2 ! $xx ! -"- старший байт !
! 3-258 ! $yy ! 256 байт данных !
+---------+----------+--------------------------+
Пакет типа 1
+---------+----------+--------------------------+
! № байта ! Значение ! Примечание !
+---------+----------+--------------------------+
! 0 ! $xx ! Способ запуска и номер !
! ! ! дисковода (0, 1, 3) !
! 1 ! $xx ! Неизвестно !
! 2 ! $xx ! Неизвестно !
! 3 ! $xx ! Страница диспетчера !
! ! ! памяти !
! 4 ! $xx ! Адрес загрузки пакета !
! 5 ! $xx ! -"- старший байт !
! 6-261 ! $yy ! 256 байт данных !
+---------+----------+--------------------------+
Ну и теперь попытка оценить все это критически.
1. Вся загрузка ведется только в режиме приема. Это создает две проблемы. Во-первых, без участия человека машину загрузить нельзя. Пусть у нас есть учительская машина и машина ученика. Учительская машина не может узнать о том, что машина ученика ждет загрузки. Кто-то должен поглядеть на экран машины ученика и махнуть учителю рукой, чтобы он начал процесс загрузки на своей машине.
Было бы намного удобнее, если бы при включении машина ученика отправила по сети широковещательное сообщение "Я машина №XX. Жду загрузку". А машина учителя ответила бы ей "ОК. Начинаю загрузку" или "Ждите, занято" или "Ваш номер не зарегистрирован". Все могло бы грузиться само, без махания руками.
Вторая проблема - невозможность подтверждения приема пакетов. По сути, вся операционка должна загрузиться целиком и без ошибок. Если хоть один пакет придет с ошибкой, то весь процесс надо начинать сначала. А ведь можно было сделать возможность отправки квитанций о приеме пакета и повторной передачи пакета при ошибке.
2. Нет контрольных сумм в пакетах. Да, формально, есть бит четности в каждом байте. Проблема в том, что он работает с нечетным числом ошибок (при искажении 1, 3, 5 или 7 бит в байте). А если искажено 2, 4, 6 или 8 бит, то контроль четности всем доволен и ошибок не видит.
3. Нет нумерации пакетов. Конечно, порядок пакетов в такой сети поменяться не может. Но вполне возможна ситуация, когда учительская машина уже начала передачу данных, а машина ученика еще не готова. Тогда машина ученика начнет прием не с начала и выяснится это только в тот момент, когда данные или код из пропущенных пакетов реально понадобятся.
4. Решение управлять диспетчером памяти напрямую и передавать адреса загрузки в заголовках пакетов - это просто рай для хакера. Можно завесить машину, передав пакет в нулевую страницу или стек. Можно замусорить таблицы устройств. Можно напрямую поуправлять устройствами ввода-вывода, передав пакет по адресам $C000 - $C7FF. Никаких проверок нет. И это может произойти даже без злого умысла, просто из-за ошибок при передаче.
5. С проверками тут вообще очень плохо. Например, при запуске управление передается на адрес $6200, но при этом никакой проверки, что в этот адрес вообще что-то было загружено - нет.
6. Отдельный вопрос, можно ли совмещать процесс загрузки одной машины и работу по сети другой. Если одна машина принимает данные, которые содержат синхросимвол другой машины, то с большой вероятностью вторая машина может загрузить чужие данные.
В общем, есть ощущение, что работоспособности добились, а про удобство и надежность забыли. Впрочем, разработчиков было немного, а работы много: сделать плату, написать прошивку, сделать новый ИКП, включая переделку каждого компонента под работу с сетью. Сроки, видимо, поджимали. В статье написано, что ОС ЛС в 1990 году получила премию министерства радиопромышленности и серебряную медаль ВДНХ. Похоже, разработчики торопились к выставке, поэтому и "срезали углы", где это возможно.